@fjall/deploy-core 4.1.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.minified +1 -1
- package/dist/src/orchestration/application/applicationDeploy.js +1 -1
- package/dist/src/orchestration/application/detectionPipeline.js +1 -1
- package/dist/src/orchestration/application/engineCompat.d.ts +74 -0
- package/dist/src/orchestration/application/engineCompat.js +1 -0
- package/dist/src/services/infrastructure/CdkService.d.ts +12 -0
- package/dist/src/services/infrastructure/CdkService.js +12 -10
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +1 -0
- package/package.json +4 -4
package/dist/.minified
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
236 files minified at 2026-07-26T05:17:55.048Z
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var ve=Object.defineProperty;var v=(e,a)=>ve(e,"name",{value:a,configurable:!0});import{join as ue}from"path";import{CloudFormationClient as Pe}from"@aws-sdk/client-cloudformation";import{EC2Client as Re}from"@aws-sdk/client-ec2";import{success as C,failure as c}from"@fjall/generator";import{logger as $}from"@fjall/util/logger";import{ImageTagSchema as Ce,getErrorMessage as ge,imageTagParameterName as Ie,maskSensitiveOutput as y}from"@fjall/util";import{parseDockerDeclarationsFromManifest as Oe}from"@fjall/util/manifest";import{stubCallerIdentity as Ae}from"../../types/deployment/index.js";import{getApplicationDeployOrder as Le,getApplicationStackName as I,getApplicationStepName as pe,getApplicationStepId as fe,APPLICATION_STACKS as P}from"../../types/operations.js";import{CdkContextBuilder as Be}from"../../services/supporting/CdkContextBuilder.js";import{buildParamsContext as Me,bootstrapOrFail as Fe}from"../contextHelpers.js";import{buildBootstrapTagsIfResolved as $e}from"../bootstrapTags.js";import{hasOrganisationTierAccount as xe}from"../../aws/organisations/accountGlobals.js";import{runDetectionPipeline as je}from"./detectionPipeline.js";import{STEP_IDS as _,STEP_NAMES as Ge}from"../../types/stepDefinitions.js";import{StepRegistry as He}from"../../steps/stepRegistry.js";import{checkTargetReadiness as Ue}from"../../aws/targetReadiness.js";import{cascadeHomeRegion as _e}from"../organisation/cascadeHelpers.js";import{withStepLifecycle as Ke}from"../stepLifecycle.js";import{DOCKER_BUILD_STEP_NAME as me,runDockerBuild as We}from"./dockerBuildHelper.js";import{getParallelPhase2Stacks as qe,getStackNamesToDeploy as Ve,deployParallelPhase as ze,deployStackSequential as Je,runDockerPreCompute as Ye,runLambdaVersionPublish as Qe,createBuildCallbacks as Xe,willRunDockerBuild as Ze}from"./applicationDeployHelpers.js";import{verifyAssemblyIntegrity as ke}from"./assemblyIntegrity.js";import{captureDatabaseConnectionOutputs as et,captureServiceTaskDefinitions as tt,reconcileDatabaseEndpointChange as rt}from"./databaseEndpointReconcile.js";import{deployCodeOnly as at}from"./codeOnlyDeploy.js";import{checkDeployPathStackAvailability as he}from"../activeDeploymentGuard.js";import{assertLeaseBeforeStack as K}from"./leaseGate.js";import{collectFullDeployArtefacts as nt}from"./artefactCollection.js";import{CfnRegistryService as ot}from"../../services/infrastructure/CfnRegistryService.js";import{ChangeSetProbe as it}from"../../services/infrastructure/changeSetProbe.js";import{runApprovalGate as st}from"./approvalGate.js";import{buildDestructionTicket as ct,runDestructionGate as lt}from"./destructionGate.js";import{DESTRUCTION_RESUME_TTL_MS as dt,signApprovalToken as ut}from"./plan/approvalToken.js";import{computeAssemblyDigest as gt}from"./plan/assemblyDigest.js";import{createMemoisedPlanLoader as pt}from"./plan/loadDeployPlan.js";import{runDriftPreFlightGate as ft}from"../drift/preFlightGate.js";import{completeForgetAfterDeploy as mt}from"../remediation/forgetResource.js";import{composePostFailureRepairOffer as ye}from"../remediation/postFailureRepairOffer.js";import{renderPinReportLines as kt,runPinRemediation as ht}from"../remediation/pinRemediation.js";import{runRecreatePreFlight as yt}from"../remediation/recreatePreFlight.js";import{runRecreateSurgery as St}from"../remediation/recreateResource.js";const x="applicationDeploy";function bt(e){const a=Object.entries(e);if(a.length===0)return;const n={};for(const[t,i]of a){const S=Ie(t);n[S]=i}return n}v(bt,"buildCdkImageTagParameters");function wt(e){if(e?.mode!==void 0&&e.mode!=="rollback"){if(e.imageTag)return c(new Error(`--image-tag requires rollback mode (explicit mode '${e.mode}' was supplied alongside an image tag)`));if(e.serviceImageTags)return c(new Error(`serviceImageTags requires rollback mode (explicit mode '${e.mode}' was supplied alongside serviceImageTags)`))}if(e?.mode==="rollback"){if(e.imageTag&&e.serviceImageTags)return c(new Error("rollback mode accepts exactly one of --image-tag or serviceImageTags \u2014 they are mutually exclusive"));if(!e.imageTag&&!e.serviceImageTags)return c(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 c(new Error("serviceImageTags already names its services \u2014 --service cannot be combined with it"));const a=Object.entries(e.serviceImageTags);if(a.length===0)return c(new Error("serviceImageTags must name at least one service"));for(const[n,t]of a){const i=Ce.safeParse(t);if(!i.success)return c(new Error(`serviceImageTags.${n} is not a valid image tag: ${i.error.message}`))}}return C(void 0)}v(wt,"validateRollbackOptions");function Se(e,a,n){if(e.onStackTargets===void 0||n.length===0)return;const t=a.awsProvider.getAccountId(),i=a.awsProvider.getRegion();e.onStackTargets(n.map(S=>({stackName:S,...t!==""?{accountId:t}:{},...i!==""?{region:i}:{}})))}v(Se,"emitStackTargets");async function lr(e,a,n){const{callbacks:t,options:i}=e,S=Date.now(),j=i?.mode??(i?.imageTag||i?.serviceImageTags?"rollback":i?.deployOnly?"code-only":"full");if(j==="restart"){const r=new Error('mode "restart" is not a deploy \u2014 call the restart() entry point (fjall rollout) instead');return t.onError?.(r),c(r)}const Y=wt(i);if(!Y.success){const r=y(Y.error.message),o=new Error(r);return t.onError?.(o),c(o)}const G=Be.buildDeploymentContext({deployType:"application",target:n.appName,path:n.path,region:a.awsProvider.getRegion(),callerIdentity:Ae(a.awsProvider.getAccountId()),...Me({orgConfig:e.orgConfig,identity:e.identity,skipOidc:e.options?.skipOidc}),...e.managedDomainBindings!==void 0?{managedDomainBindings:e.managedDomainBindings}:{}},{verbose:i?.verbose,infraOnly:i?.infraOnly},e.orgConfig),T=a.frameworkRegistry.resolve({appPath:n.path});let Q;const L=j==="rollback";if(T&&!L){Q=T.builder.plan({appPath:n.path},T.detection);const r=Xe(t),o=await T.builder.build(n.path,Q,r,{skipBuild:i?.skipBuild,infraOnly:i?.infraOnly,...e.abortSignal!==void 0&&{abortSignal:e.abortSignal}});if(!o.success){const s=new Error(y(o.error.message));return t.onError?.(s),c(s)}}if(j==="code-only"||L){t.onLog?.(L?i?.serviceImageTags?`Rollback mode \u2014 rolling ${Object.keys(i.serviceImageTags).length} service(s) to their release-recorded image tags`:`Rollback mode \u2014 rolling to image tag ${i?.imageTag}`:"Deploy-only mode \u2014 skipping infrastructure pipeline","info");const r=ue(n.path,"cdk.out"),o=Oe(r),s=o.declaresBuild,p=T?.detection.hasDockerfile===!0,l=s||p;$.debug(x,"Deploy-only branch entered",{mode:j,imageTag:i?.imageTag,serviceImageTags:i?.serviceImageTags,appName:n.appName,appPath:n.path,cdkOutPath:r,dockerProviderAvailable:e.dockerProvider!==void 0,builderName:T?.builder.name,hasDockerfileFromManifest:s,hasDockerfileFromDisk:p,hasDockerfile:l,manifestDockerServiceCount:o.ecsServices.length,manifestDockerPaths:o.ecsServices.map(f=>f.docker.path),manifestLambdaDockerEntryCount:o.lambdaEntries.length}),!s&&!p&&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");const u=await he([I(n.appName,P.COMPUTE)],a.cfnService,e.abortSignal);if(!u.success)return t.onError?.(u.error),c(u.error);Se(t,a,[I(n.appName,P.COMPUTE)]);let k={},d;if(!L&&e.dockerProvider!==void 0&&l){$.debug(x,"Running Docker build before code-only deploy",{source:s?"manifest":"disk"});const f=await We(e,a,n,t);if(!f.success)return c(f.error);k=f.data.contentHashTagsByService,d=f.data}else $.debug(x,"Skipping Docker build",{reason:L?"rollback":e.dockerProvider===void 0?"no dockerProvider":"no Dockerfile detected"});const m=await K(e.assertLeaseForStack,I(n.appName,P.COMPUTE),e.abortSignal);return m!==void 0?(t.onError?.(m),c(m)):at(e,a,n,k,d)}t.onLog?.("Analysing infrastructure\u2026","info");const H=await je(n,a,G,t,e.abortSignal);if(!H.success){const r=new Error(y(H.error.message),{cause:H.error});return t.onError?.(r),c(r)}const g=H.data;try{await t.onDetectionComplete?.({...g,builderName:T?.builder.name??"unknown"})}catch(r){const o=new Error(y(ge(r)),{cause:r});return t.onError?.(o),c(o)}const be={deploymentType:"application",operation:"deploy",deployOnly:!1,infraOnly:i?.infraOnly??!1,hasDockerfile:g.hasDockerfile,pattern:g.pattern,resources:g.resources,...T&&{builderName:T.builder.name}},X=He.getSteps(be),Z=X.findIndex(r=>r.id===_.TARGET_READINESS),ee=await Ke(t,{stepId:_.TARGET_READINESS,stepName:Ge.TARGET_READINESS,...Z>=0&&{stepIndex:Z,totalSteps:X.length}},async()=>{if(i?.skipReadinessCheck)return t.onLog?.("Skipping target readiness check (--skip-readiness-check)","warn"),{kind:"skipped",data:void 0};const r=a.awsProvider.getAccountId(),o=e.orgConfig?.providerAccounts.find(u=>u.id===r)?.name,s=a.awsProvider.getCredentials(),p=_e(e.orgConfig),l=await Ue({cloudFormation:a.awsProvider.getClient(Pe),ec2:new Re({region:p,...s!==void 0&&{credentials:s}})},{id:r,...o!==void 0&&{name:o}},a.awsProvider.getRegion(),e.orgConfig,e.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),{cause:l.error})}},e.abortSignal);if(!ee.success)return ee;const N=Le({pattern:g.pattern,resources:g.resources}),O=N.length,te=g.hasDockerfile&&e.dockerProvider!==void 0&&N.includes(P.COMPUTE)&&i?.infraOnly!==!0;if(!g.hasDifferences&&!i?.force&&!te){t.onLog?.("No infrastructure changes detected","info"),t.onLog?.(i?.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 r=g.hasDockerfile&&e.dockerProvider!==void 0&&N.includes(P.COMPUTE);for(let s=0;s<N.length;s++){const p=N[s];r&&p===P.COMPUTE&&(t.onStepStart?.(_.DOCKER_OPERATIONS,me),t.onStepComplete?.(_.DOCKER_OPERATIONS,me,"skipped"));const l=fe(p,"deploy"),u=pe(p,"deploy");t.onStepStart?.(l,u,s,O),t.onStepComplete?.(l,u,"skipped",s,O)}const o=await a.stackService.resolveWebsiteUrl(n.appName);return C({target:n.appName,deploymentType:"application",outputs:o?{websiteUrl:o}:void 0,noChanges:!0,artefacts:[],durationMs:Date.now()-S})}const A=ue(n.path,"cdk.out"),b=Ve({deployOrder:N,stackChanges:g.stackChanges,appName:n.appName,force:i?.force===!0,computeBuildPossible:te});Se(t,a,[...b]);const W=await he([...b],a.cfnService,e.abortSignal);if(!W.success)return t.onError?.(W.error),c(W.error);const re={stacksToDeploy:[...b],imageBuild:Ze({stackNamesToDeploy:b,appName:n.appName,hasDockerProvider:e.dockerProvider!==void 0,hasDockerfile:g.hasDockerfile})},ae=gt(g.currentHashes,re),q=pt({cfnService:a.cfnService,cdkOutPath:A,changedStacks:[...b],assemblyDigest:ae,registry:new ot(a.awsProvider),changeSetProbe:new it(a.awsProvider),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}),V=await ft(a.awsProvider,{stackNames:[...b],cdkOutPath:A,cfnService:a.cfnService,callbacks:t,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(V.kind==="blocked"){const r=new Error(V.message);return t.onError?.(r),c(r)}const ne=V.verifiedAbsentByNecessity??[],U=e.approvalGate!==void 0||i?.approvalToken!==void 0&&i.approvalToken!==""||i?.planOnly===!0;if(U){const r=await st({approvalGate:e.approvalGate,options:i??{},target:n.appName,cdkOutPath:A,detection:{stackChanges:g.stackChanges,currentHashes:g.currentHashes},scope:re,cfnService:a.cfnService,loadPlan:q,callbacks:t,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(!r.success)return r;const o=r.data;if(o.kind==="rejected")return t.onLog?.(`Deploy rejected: ${y(o.reason)}`,"warn"),C({target:n.appName,deploymentType:"application",rejected:!0,artefacts:[],...o.planDetail!==void 0?{planDetail:o.planDetail}:{},durationMs:Date.now()-S});if(o.kind==="awaiting"){const s=await q(),p=s.success?ct(s.data,ne):void 0;return C({target:n.appName,deploymentType:"application",awaitingApproval:!0,approvalToken:o.approvalToken,assemblyDigest:o.assemblyDigest,planDetail:o.planDetail,...p!==void 0?{destructionTicket:p}:{},artefacts:[],durationMs:Date.now()-S})}}const E=await lt({loadPlan:q,options:i??{},target:n.appName,callbacks:t,verifiedAbsentByNecessity:ne,...e.destructionConsentGate!==void 0?{consentGate:e.destructionConsentGate}:{}});if(!E.success)return t.onError?.(E.error),c(E.error);if(E.data.kind==="withheld"){const r=U?ut({assemblyDigest:ae,ttlMs:dt}):void 0;return C({target:n.appName,deploymentType:"application",destructionPending:!0,destructionTicket:E.data.ticket,destructionOutcome:E.data.outcome,destructionHaltReasons:E.data.reasons,...r!==void 0?{destructionResumeToken:r.token,destructionResumeExpiresAt:r.expiresAt}:{},artefacts:[],durationMs:Date.now()-S})}if(E.data.kind==="proceed"){const{ticket:r,verbsByPhysicalName:o}=E.data,s=r.resources.filter(l=>o.get(l.physicalName)==="pin");if(s.length>0){t.onLog?.(`Pin remediation for ${n.appName}: probing live value(s) for ${s.length} consented pin(s) \u2014 deploy stopped for config write-back; nothing was applied`,"warn");const l=await ht(a.awsProvider,{targets:s.map(u=>({stack:u.stack,logicalId:u.logicalId,resourceType:u.resourceType,physicalName:u.physicalName,offendingProperties:u.offendingProperties})),...e.infrastructureSource!==void 0?{infrastructureContent:e.infrastructureSource.content,infrastructureFilePath:e.infrastructureSource.filePath}:{},...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});for(const u of kt(l))t.onLog?.(u,"warn");return s.length<r.resources.length&&t.onLog?.("Remaining consented change(s) were not applied \u2014 the config write-back changes the plan, so they re-consent on the next deploy","warn"),C({target:n.appName,deploymentType:"application",pinPending:!0,pinReport:l,pinDeferredConsents:r.resources.length-s.length,destructionTicket:r,artefacts:[],durationMs:Date.now()-S})}const p=r.resources.filter(l=>l.finding==="pinned-name-replacement"&&o.get(l.physicalName)==="recreate");if(p.length>0){const l=new Map;for(const d of p){const m=l.get(d.stack)??[];m.push(d),l.set(d.stack,m)}const u=await yt(a.awsProvider,{stacks:[...l].map(([d,m])=>({stackName:d,targets:m.map(f=>({logicalId:f.logicalId,resourceType:f.resourceType,physicalId:f.physicalName}))})),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(!u.success){const d=new Error(y(u.error.message),{cause:u.error});return t.onError?.(d),c(d)}const k=[];for(const[d,m]of l){const f=await K(e.assertLeaseForStack,d,e.abortSignal,"destroy");if(f!==void 0)return k.length>0&&t.onLog?.(`Surgery already completed on ${k.join("; ")} \u2014 those resources were removed and re-running the deploy resumes their journals and re-creates them.`,"warn"),t.onError?.(f),c(f);const R=m.map(h=>h.physicalName);t.onLog?.(`Recreate remediation for ${n.appName}: orchestrating the pinned-name replacement of ${R.join(", ")} on ${d}`,"warn");const F=await St(a.awsProvider,{stackName:d,targets:m.map(h=>({logicalId:h.logicalId,resourceType:h.resourceType,physicalId:h.physicalName})),appName:n.appName,consentedPhysicalNames:R,doneJournalPolicy:"archive-and-restart",plannedReplacementLogicalIds:m.map(h=>h.logicalId),onProgress:v(h=>t.onLog?.(h,"info"),"onProgress"),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(!F.success){const h=k.length>0?` Surgery already completed on ${k.join("; ")} \u2014 those resources were removed and re-running the deploy resumes their journals and re-creates them.`:"",w=new Error(y(`${F.error.message}${h}`));return t.onError?.(w),c(w)}k.push(`${d} (${R.join(", ")})`)}}}const we=e.orgConfig!==void 0&&!xe(e.orgConfig.providerAccounts),Te=$e({account:e.orgConfig?.providerAccounts.find(r=>r.id===a.awsProvider.getAccountId()),estate:we?"solo":"org"}),oe=await Fe(a,G,t,Te);if(!oe.success)return oe;const ie=I(n.appName,P.DATABASE),z=b.has(ie),Ne=z?await et(a.cfnService,n.appName,e.abortSignal):void 0,Ee=z?await tt(a.ecsResolver,a.ecsService,n.appName,e.abortSignal):void 0,B={},M=new Map;let se,ce={};const D=v(async()=>{!z||!M.has(ie)||await rt({appName:n.appName,connectionOutputsBefore:Ne,serviceTaskDefinitionsBefore:Ee,services:{cfnService:a.cfnService,ecsService:a.ecsService,ecsResolver:a.ecsResolver},callbacks:t,...e.confirmDatabaseEndpointRestart!==void 0?{confirmRestart:e.confirmDatabaseEndpointRestart}:{},...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}})},"reconcileIfDatabaseReplaced");for(let r=0;r<N.length;r++){const o=N[r],s=I(n.appName,o),p=fe(o,"deploy"),l=pe(o,"deploy");if(!b.has(s)){t.onStepStart?.(p,l,r,O),t.onLog?.(`Skipping ${o} \u2014 no changes detected`,"info"),t.onStepComplete?.(p,l,"skipped",r,O);continue}const u=await K(e.assertLeaseForStack,s,e.abortSignal);if(u!==void 0)return await D(),t.onError?.(u),c(u);const k=qe(N,r,n.appName,b);if(k.length>=2){const w=await ke({hashService:a.hashService,cdkOutPath:A,stackNames:k.map(J=>I(n.appName,J)),detectionHashes:g.currentHashes,approvalGated:U});if(!w.success)return await D(),t.onError?.(w.error),c(w.error);const de=await ze(k,n,a,G,t,r,O,g,B,M);if(!de.success)return await D(),c(await ye(a.awsProvider,{stackNames:k.map(J=>I(n.appName,J)),appName:n.appName,originalError:de.error,cfnService:a.cfnService,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}));r+=k.length-1;continue}const d=await Ye(o,e,a,n,t,g.hasDockerfile);if(d!==null&&!d.success)return await D(),c(d.error);d!==null&&d.success&&(se=d.data);const m=d!==null&&d.success?d.data.contentHashTagsByService:{},f=bt(m);if(d!==null){const w=await K(e.assertLeaseForStack,s,e.abortSignal);if(w!==void 0)return await D(),t.onError?.(w),c(w)}const R=await ke({hashService:a.hashService,cdkOutPath:A,stackNames:[s],detectionHashes:g.currentHashes,approvalGated:U});if(!R.success)return await D(),t.onError?.(R.error),c(R.error);const F=await Je(o,a,G,t,r,O,B,f!==void 0||e.abortSignal!==void 0?{...f!==void 0?{parameters:f}:{},...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}:void 0);if(!F.success)return await D(),c(await ye(a.awsProvider,{stackNames:[s],appName:n.appName,originalError:F.error,cfnService:a.cfnService,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}));o===P.COMPUTE&&(ce=await Qe(n,a,t,m,e.abortSignal));const h=g.currentHashes.get(s);h&&M.set(s,h)}if(await D(),M.size>0){const r=await a.hashService.updateStateAfterDeploy(n.path,M);if(!r.success){const o=y(r.error.message);$.debug(x,"Failed to update state file",{error:o}),t.onLog?.(`Warning: failed to update state file \u2014 next deploy may re-deploy unchanged stacks: ${o}`,"warn")}}const le=await a.stackService.resolveWebsiteUrl(n.appName);le&&(B.websiteUrl=le);const De=await nt(a,n.appName,se,t,A,ce,e.abortSignal);return await mt(a.awsProvider,{stackNames:[...b],appName:n.appName,onLog:v((r,o)=>t.onLog?.(r,o),"onLog"),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}).catch(r=>{$.debug(x,"Forget completion sweep failed",{error:y(ge(r))})}),C({target:n.appName,deploymentType:"application",outputs:Object.keys(B).length>0?B:void 0,artefacts:De,durationMs:Date.now()-S})}v(lr,"deployApplication");export{lr as deployApplication,Se as emitStackTargets};
|
|
1
|
+
var ve=Object.defineProperty;var v=(e,a)=>ve(e,"name",{value:a,configurable:!0});import{join as ue}from"path";import{CloudFormationClient as Pe}from"@aws-sdk/client-cloudformation";import{EC2Client as Re}from"@aws-sdk/client-ec2";import{success as C,failure as c}from"@fjall/generator";import{logger as $}from"@fjall/util/logger";import{ImageTagSchema as Ce,getErrorMessage as ge,imageTagParameterName as Ie,maskSensitiveOutput as S}from"@fjall/util";import{parseDockerDeclarationsFromManifest as Oe}from"@fjall/util/manifest";import{stubCallerIdentity as Ae}from"../../types/deployment/index.js";import{getApplicationDeployOrder as Le,getApplicationStackName as I,getApplicationStepName as pe,getApplicationStepId as fe,APPLICATION_STACKS as R}from"../../types/operations.js";import{CdkContextBuilder as Be}from"../../services/supporting/CdkContextBuilder.js";import{buildParamsContext as Me,bootstrapOrFail as Fe}from"../contextHelpers.js";import{buildBootstrapTagsIfResolved as $e}from"../bootstrapTags.js";import{hasOrganisationTierAccount as xe}from"../../aws/organisations/accountGlobals.js";import{runDetectionPipeline as je}from"./detectionPipeline.js";import{assertEngineCompatibleWithAssembly as Ge}from"./engineCompat.js";import{STEP_IDS as _,STEP_NAMES as He}from"../../types/stepDefinitions.js";import{StepRegistry as Ue}from"../../steps/stepRegistry.js";import{checkTargetReadiness as _e}from"../../aws/targetReadiness.js";import{cascadeHomeRegion as We}from"../organisation/cascadeHelpers.js";import{withStepLifecycle as Ke}from"../stepLifecycle.js";import{DOCKER_BUILD_STEP_NAME as me,runDockerBuild as qe}from"./dockerBuildHelper.js";import{getParallelPhase2Stacks as Ve,getStackNamesToDeploy as ze,deployParallelPhase as Je,deployStackSequential as Ye,runDockerPreCompute as Qe,runLambdaVersionPublish as Xe,createBuildCallbacks as Ze,willRunDockerBuild as et}from"./applicationDeployHelpers.js";import{verifyAssemblyIntegrity as ke}from"./assemblyIntegrity.js";import{captureDatabaseConnectionOutputs as tt,captureServiceTaskDefinitions as rt,reconcileDatabaseEndpointChange as at}from"./databaseEndpointReconcile.js";import{deployCodeOnly as nt}from"./codeOnlyDeploy.js";import{checkDeployPathStackAvailability as he}from"../activeDeploymentGuard.js";import{assertLeaseBeforeStack as W}from"./leaseGate.js";import{collectFullDeployArtefacts as ot}from"./artefactCollection.js";import{CfnRegistryService as it}from"../../services/infrastructure/CfnRegistryService.js";import{ChangeSetProbe as st}from"../../services/infrastructure/changeSetProbe.js";import{runApprovalGate as ct}from"./approvalGate.js";import{buildDestructionTicket as lt,runDestructionGate as dt}from"./destructionGate.js";import{DESTRUCTION_RESUME_TTL_MS as ut,signApprovalToken as gt}from"./plan/approvalToken.js";import{computeAssemblyDigest as pt}from"./plan/assemblyDigest.js";import{createMemoisedPlanLoader as ft}from"./plan/loadDeployPlan.js";import{runDriftPreFlightGate as mt}from"../drift/preFlightGate.js";import{completeForgetAfterDeploy as kt}from"../remediation/forgetResource.js";import{composePostFailureRepairOffer as ye}from"../remediation/postFailureRepairOffer.js";import{renderPinReportLines as ht,runPinRemediation as yt}from"../remediation/pinRemediation.js";import{runRecreatePreFlight as St}from"../remediation/recreatePreFlight.js";import{runRecreateSurgery as bt}from"../remediation/recreateResource.js";const x="applicationDeploy";function wt(e){const a=Object.entries(e);if(a.length===0)return;const n={};for(const[t,i]of a){const b=Ie(t);n[b]=i}return n}v(wt,"buildCdkImageTagParameters");function Tt(e){if(e?.mode!==void 0&&e.mode!=="rollback"){if(e.imageTag)return c(new Error(`--image-tag requires rollback mode (explicit mode '${e.mode}' was supplied alongside an image tag)`));if(e.serviceImageTags)return c(new Error(`serviceImageTags requires rollback mode (explicit mode '${e.mode}' was supplied alongside serviceImageTags)`))}if(e?.mode==="rollback"){if(e.imageTag&&e.serviceImageTags)return c(new Error("rollback mode accepts exactly one of --image-tag or serviceImageTags \u2014 they are mutually exclusive"));if(!e.imageTag&&!e.serviceImageTags)return c(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 c(new Error("serviceImageTags already names its services \u2014 --service cannot be combined with it"));const a=Object.entries(e.serviceImageTags);if(a.length===0)return c(new Error("serviceImageTags must name at least one service"));for(const[n,t]of a){const i=Ce.safeParse(t);if(!i.success)return c(new Error(`serviceImageTags.${n} is not a valid image tag: ${i.error.message}`))}}return C(void 0)}v(Tt,"validateRollbackOptions");function Se(e,a,n){if(e.onStackTargets===void 0||n.length===0)return;const t=a.awsProvider.getAccountId(),i=a.awsProvider.getRegion();e.onStackTargets(n.map(b=>({stackName:b,...t!==""?{accountId:t}:{},...i!==""?{region:i}:{}})))}v(Se,"emitStackTargets");async function ur(e,a,n){const{callbacks:t,options:i}=e,b=Date.now(),j=i?.mode??(i?.imageTag||i?.serviceImageTags?"rollback":i?.deployOnly?"code-only":"full");if(j==="restart"){const r=new Error('mode "restart" is not a deploy \u2014 call the restart() entry point (fjall rollout) instead');return t.onError?.(r),c(r)}const Y=Tt(i);if(!Y.success){const r=S(Y.error.message),o=new Error(r);return t.onError?.(o),c(o)}const G=Be.buildDeploymentContext({deployType:"application",target:n.appName,path:n.path,region:a.awsProvider.getRegion(),callerIdentity:Ae(a.awsProvider.getAccountId()),...Me({orgConfig:e.orgConfig,identity:e.identity,skipOidc:e.options?.skipOidc}),...e.managedDomainBindings!==void 0?{managedDomainBindings:e.managedDomainBindings}:{}},{verbose:i?.verbose,infraOnly:i?.infraOnly},e.orgConfig),E=a.frameworkRegistry.resolve({appPath:n.path});let Q;const L=j==="rollback";if(E&&!L){Q=E.builder.plan({appPath:n.path},E.detection);const r=Ze(t),o=await E.builder.build(n.path,Q,r,{skipBuild:i?.skipBuild,infraOnly:i?.infraOnly,...e.abortSignal!==void 0&&{abortSignal:e.abortSignal}});if(!o.success){const s=new Error(S(o.error.message));return t.onError?.(s),c(s)}}if(j==="code-only"||L){t.onLog?.(L?i?.serviceImageTags?`Rollback mode \u2014 rolling ${Object.keys(i.serviceImageTags).length} service(s) to their release-recorded image tags`:`Rollback mode \u2014 rolling to image tag ${i?.imageTag}`:"Deploy-only mode \u2014 skipping infrastructure pipeline","info");const r=ue(n.path,"cdk.out"),o=Ge({cdkOutPath:r,onWarning:v(f=>t.onLog?.(f,"warn"),"onWarning")});if(!o.success){const f=new Error(S(o.error.message));return t.onError?.(f),c(f)}const s=Oe(r),p=s.declaresBuild,l=E?.detection.hasDockerfile===!0,u=p||l;$.debug(x,"Deploy-only branch entered",{mode:j,imageTag:i?.imageTag,serviceImageTags:i?.serviceImageTags,appName:n.appName,appPath:n.path,cdkOutPath:r,dockerProviderAvailable:e.dockerProvider!==void 0,builderName:E?.builder.name,hasDockerfileFromManifest:p,hasDockerfileFromDisk:l,hasDockerfile:u,manifestDockerServiceCount:s.ecsServices.length,manifestDockerPaths:s.ecsServices.map(f=>f.docker.path),manifestLambdaDockerEntryCount:s.lambdaEntries.length}),!p&&!l&&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");const m=await he([I(n.appName,R.COMPUTE)],a.cfnService,e.abortSignal);if(!m.success)return t.onError?.(m.error),c(m.error);Se(t,a,[I(n.appName,R.COMPUTE)]);let d={},h;if(!L&&e.dockerProvider!==void 0&&u){$.debug(x,"Running Docker build before code-only deploy",{source:p?"manifest":"disk"});const f=await qe(e,a,n,t);if(!f.success)return c(f.error);d=f.data.contentHashTagsByService,h=f.data}else $.debug(x,"Skipping Docker build",{reason:L?"rollback":e.dockerProvider===void 0?"no dockerProvider":"no Dockerfile detected"});const k=await W(e.assertLeaseForStack,I(n.appName,R.COMPUTE),e.abortSignal);return k!==void 0?(t.onError?.(k),c(k)):nt(e,a,n,d,h)}t.onLog?.("Analysing infrastructure\u2026","info");const H=await je(n,a,G,t,e.abortSignal);if(!H.success){const r=new Error(S(H.error.message),{cause:H.error});return t.onError?.(r),c(r)}const g=H.data;try{await t.onDetectionComplete?.({...g,builderName:E?.builder.name??"unknown"})}catch(r){const o=new Error(S(ge(r)),{cause:r});return t.onError?.(o),c(o)}const be={deploymentType:"application",operation:"deploy",deployOnly:!1,infraOnly:i?.infraOnly??!1,hasDockerfile:g.hasDockerfile,pattern:g.pattern,resources:g.resources,...E&&{builderName:E.builder.name}},X=Ue.getSteps(be),Z=X.findIndex(r=>r.id===_.TARGET_READINESS),ee=await Ke(t,{stepId:_.TARGET_READINESS,stepName:He.TARGET_READINESS,...Z>=0&&{stepIndex:Z,totalSteps:X.length}},async()=>{if(i?.skipReadinessCheck)return t.onLog?.("Skipping target readiness check (--skip-readiness-check)","warn"),{kind:"skipped",data:void 0};const r=a.awsProvider.getAccountId(),o=e.orgConfig?.providerAccounts.find(u=>u.id===r)?.name,s=a.awsProvider.getCredentials(),p=We(e.orgConfig),l=await _e({cloudFormation:a.awsProvider.getClient(Pe),ec2:new Re({region:p,...s!==void 0&&{credentials:s}})},{id:r,...o!==void 0&&{name:o}},a.awsProvider.getRegion(),e.orgConfig,e.abortSignal);return l.success?l.data.ready?{kind:"completed",data:void 0}:{kind:"error",error:new Error(S(l.data.advisory))}:{kind:"error",error:new Error(S(l.error.message),{cause:l.error})}},e.abortSignal);if(!ee.success)return ee;const N=Le({pattern:g.pattern,resources:g.resources}),O=N.length,te=g.hasDockerfile&&e.dockerProvider!==void 0&&N.includes(R.COMPUTE)&&i?.infraOnly!==!0;if(!g.hasDifferences&&!i?.force&&!te){t.onLog?.("No infrastructure changes detected","info"),t.onLog?.(i?.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 r=g.hasDockerfile&&e.dockerProvider!==void 0&&N.includes(R.COMPUTE);for(let s=0;s<N.length;s++){const p=N[s];r&&p===R.COMPUTE&&(t.onStepStart?.(_.DOCKER_OPERATIONS,me),t.onStepComplete?.(_.DOCKER_OPERATIONS,me,"skipped"));const l=fe(p,"deploy"),u=pe(p,"deploy");t.onStepStart?.(l,u,s,O),t.onStepComplete?.(l,u,"skipped",s,O)}const o=await a.stackService.resolveWebsiteUrl(n.appName);return C({target:n.appName,deploymentType:"application",outputs:o?{websiteUrl:o}:void 0,noChanges:!0,artefacts:[],durationMs:Date.now()-b})}const A=ue(n.path,"cdk.out"),w=ze({deployOrder:N,stackChanges:g.stackChanges,appName:n.appName,force:i?.force===!0,computeBuildPossible:te});Se(t,a,[...w]);const K=await he([...w],a.cfnService,e.abortSignal);if(!K.success)return t.onError?.(K.error),c(K.error);const re={stacksToDeploy:[...w],imageBuild:et({stackNamesToDeploy:w,appName:n.appName,hasDockerProvider:e.dockerProvider!==void 0,hasDockerfile:g.hasDockerfile})},ae=pt(g.currentHashes,re),q=ft({cfnService:a.cfnService,cdkOutPath:A,changedStacks:[...w],assemblyDigest:ae,registry:new it(a.awsProvider),changeSetProbe:new st(a.awsProvider),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}),V=await mt(a.awsProvider,{stackNames:[...w],cdkOutPath:A,cfnService:a.cfnService,callbacks:t,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(V.kind==="blocked"){const r=new Error(V.message);return t.onError?.(r),c(r)}const ne=V.verifiedAbsentByNecessity??[],U=e.approvalGate!==void 0||i?.approvalToken!==void 0&&i.approvalToken!==""||i?.planOnly===!0;if(U){const r=await ct({approvalGate:e.approvalGate,options:i??{},target:n.appName,cdkOutPath:A,detection:{stackChanges:g.stackChanges,currentHashes:g.currentHashes},scope:re,cfnService:a.cfnService,loadPlan:q,callbacks:t,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(!r.success)return r;const o=r.data;if(o.kind==="rejected")return t.onLog?.(`Deploy rejected: ${S(o.reason)}`,"warn"),C({target:n.appName,deploymentType:"application",rejected:!0,artefacts:[],...o.planDetail!==void 0?{planDetail:o.planDetail}:{},durationMs:Date.now()-b});if(o.kind==="awaiting"){const s=await q(),p=s.success?lt(s.data,ne):void 0;return C({target:n.appName,deploymentType:"application",awaitingApproval:!0,approvalToken:o.approvalToken,assemblyDigest:o.assemblyDigest,planDetail:o.planDetail,...p!==void 0?{destructionTicket:p}:{},artefacts:[],durationMs:Date.now()-b})}}const D=await dt({loadPlan:q,options:i??{},target:n.appName,callbacks:t,verifiedAbsentByNecessity:ne,...e.destructionConsentGate!==void 0?{consentGate:e.destructionConsentGate}:{}});if(!D.success)return t.onError?.(D.error),c(D.error);if(D.data.kind==="withheld"){const r=U?gt({assemblyDigest:ae,ttlMs:ut}):void 0;return C({target:n.appName,deploymentType:"application",destructionPending:!0,destructionTicket:D.data.ticket,destructionOutcome:D.data.outcome,destructionHaltReasons:D.data.reasons,...r!==void 0?{destructionResumeToken:r.token,destructionResumeExpiresAt:r.expiresAt}:{},artefacts:[],durationMs:Date.now()-b})}if(D.data.kind==="proceed"){const{ticket:r,verbsByPhysicalName:o}=D.data,s=r.resources.filter(l=>o.get(l.physicalName)==="pin");if(s.length>0){t.onLog?.(`Pin remediation for ${n.appName}: probing live value(s) for ${s.length} consented pin(s) \u2014 deploy stopped for config write-back; nothing was applied`,"warn");const l=await yt(a.awsProvider,{targets:s.map(u=>({stack:u.stack,logicalId:u.logicalId,resourceType:u.resourceType,physicalName:u.physicalName,offendingProperties:u.offendingProperties})),...e.infrastructureSource!==void 0?{infrastructureContent:e.infrastructureSource.content,infrastructureFilePath:e.infrastructureSource.filePath}:{},...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});for(const u of ht(l))t.onLog?.(u,"warn");return s.length<r.resources.length&&t.onLog?.("Remaining consented change(s) were not applied \u2014 the config write-back changes the plan, so they re-consent on the next deploy","warn"),C({target:n.appName,deploymentType:"application",pinPending:!0,pinReport:l,pinDeferredConsents:r.resources.length-s.length,destructionTicket:r,artefacts:[],durationMs:Date.now()-b})}const p=r.resources.filter(l=>l.finding==="pinned-name-replacement"&&o.get(l.physicalName)==="recreate");if(p.length>0){const l=new Map;for(const d of p){const h=l.get(d.stack)??[];h.push(d),l.set(d.stack,h)}const u=await St(a.awsProvider,{stacks:[...l].map(([d,h])=>({stackName:d,targets:h.map(k=>({logicalId:k.logicalId,resourceType:k.resourceType,physicalId:k.physicalName}))})),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(!u.success){const d=new Error(S(u.error.message),{cause:u.error});return t.onError?.(d),c(d)}const m=[];for(const[d,h]of l){const k=await W(e.assertLeaseForStack,d,e.abortSignal,"destroy");if(k!==void 0)return m.length>0&&t.onLog?.(`Surgery already completed on ${m.join("; ")} \u2014 those resources were removed and re-running the deploy resumes their journals and re-creates them.`,"warn"),t.onError?.(k),c(k);const f=h.map(y=>y.physicalName);t.onLog?.(`Recreate remediation for ${n.appName}: orchestrating the pinned-name replacement of ${f.join(", ")} on ${d}`,"warn");const F=await bt(a.awsProvider,{stackName:d,targets:h.map(y=>({logicalId:y.logicalId,resourceType:y.resourceType,physicalId:y.physicalName})),appName:n.appName,consentedPhysicalNames:f,doneJournalPolicy:"archive-and-restart",plannedReplacementLogicalIds:h.map(y=>y.logicalId),onProgress:v(y=>t.onLog?.(y,"info"),"onProgress"),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(!F.success){const y=m.length>0?` Surgery already completed on ${m.join("; ")} \u2014 those resources were removed and re-running the deploy resumes their journals and re-creates them.`:"",T=new Error(S(`${F.error.message}${y}`));return t.onError?.(T),c(T)}m.push(`${d} (${f.join(", ")})`)}}}const we=e.orgConfig!==void 0&&!xe(e.orgConfig.providerAccounts),Te=$e({account:e.orgConfig?.providerAccounts.find(r=>r.id===a.awsProvider.getAccountId()),estate:we?"solo":"org"}),oe=await Fe(a,G,t,Te);if(!oe.success)return oe;const ie=I(n.appName,R.DATABASE),z=w.has(ie),Ee=z?await tt(a.cfnService,n.appName,e.abortSignal):void 0,Ne=z?await rt(a.ecsResolver,a.ecsService,n.appName,e.abortSignal):void 0,B={},M=new Map;let se,ce={};const P=v(async()=>{!z||!M.has(ie)||await at({appName:n.appName,connectionOutputsBefore:Ee,serviceTaskDefinitionsBefore:Ne,services:{cfnService:a.cfnService,ecsService:a.ecsService,ecsResolver:a.ecsResolver},callbacks:t,...e.confirmDatabaseEndpointRestart!==void 0?{confirmRestart:e.confirmDatabaseEndpointRestart}:{},...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}})},"reconcileIfDatabaseReplaced");for(let r=0;r<N.length;r++){const o=N[r],s=I(n.appName,o),p=fe(o,"deploy"),l=pe(o,"deploy");if(!w.has(s)){t.onStepStart?.(p,l,r,O),t.onLog?.(`Skipping ${o} \u2014 no changes detected`,"info"),t.onStepComplete?.(p,l,"skipped",r,O);continue}const u=await W(e.assertLeaseForStack,s,e.abortSignal);if(u!==void 0)return await P(),t.onError?.(u),c(u);const m=Ve(N,r,n.appName,w);if(m.length>=2){const T=await ke({hashService:a.hashService,cdkOutPath:A,stackNames:m.map(J=>I(n.appName,J)),detectionHashes:g.currentHashes,approvalGated:U});if(!T.success)return await P(),t.onError?.(T.error),c(T.error);const de=await Je(m,n,a,G,t,r,O,g,B,M);if(!de.success)return await P(),c(await ye(a.awsProvider,{stackNames:m.map(J=>I(n.appName,J)),appName:n.appName,originalError:de.error,cfnService:a.cfnService,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}));r+=m.length-1;continue}const d=await Qe(o,e,a,n,t,g.hasDockerfile);if(d!==null&&!d.success)return await P(),c(d.error);d!==null&&d.success&&(se=d.data);const h=d!==null&&d.success?d.data.contentHashTagsByService:{},k=wt(h);if(d!==null){const T=await W(e.assertLeaseForStack,s,e.abortSignal);if(T!==void 0)return await P(),t.onError?.(T),c(T)}const f=await ke({hashService:a.hashService,cdkOutPath:A,stackNames:[s],detectionHashes:g.currentHashes,approvalGated:U});if(!f.success)return await P(),t.onError?.(f.error),c(f.error);const F=await Ye(o,a,G,t,r,O,B,k!==void 0||e.abortSignal!==void 0?{...k!==void 0?{parameters:k}:{},...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}:void 0);if(!F.success)return await P(),c(await ye(a.awsProvider,{stackNames:[s],appName:n.appName,originalError:F.error,cfnService:a.cfnService,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}));o===R.COMPUTE&&(ce=await Xe(n,a,t,h,e.abortSignal));const y=g.currentHashes.get(s);y&&M.set(s,y)}if(await P(),M.size>0){const r=await a.hashService.updateStateAfterDeploy(n.path,M);if(!r.success){const o=S(r.error.message);$.debug(x,"Failed to update state file",{error:o}),t.onLog?.(`Warning: failed to update state file \u2014 next deploy may re-deploy unchanged stacks: ${o}`,"warn")}}const le=await a.stackService.resolveWebsiteUrl(n.appName);le&&(B.websiteUrl=le);const De=await ot(a,n.appName,se,t,A,ce,e.abortSignal);return await kt(a.awsProvider,{stackNames:[...w],appName:n.appName,onLog:v((r,o)=>t.onLog?.(r,o),"onLog"),...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}}).catch(r=>{$.debug(x,"Forget completion sweep failed",{error:S(ge(r))})}),C({target:n.appName,deploymentType:"application",outputs:Object.keys(B).length>0?B:void 0,artefacts:De,durationMs:Date.now()-b})}v(ur,"deployApplication");export{ur as deployApplication,Se as emitStackTargets};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var F=Object.defineProperty;var S=(o,r)=>F(o,"name",{value:r,configurable:!0});import{join as H}from"path";import{success as M,failure as s}from"@fjall/generator";import{logger as O}from"@fjall/util/logger";import{maskSensitiveOutput as n}from"@fjall/util";import{deriveResourcesFromManifestStacks as L}from"../../types/detection/patternDetection.js";import{emitProgress as R,PROGRESS_MESSAGES as E}from"../../services/supporting/helpers.js";import{parseRequiredSecretsFromManifest as q,parseImportedSecretNamesFromManifest as T}from"../manifestSecretParser.js";import{resolveSecretArns as W}from"../secretArnResolver.js";import{isAborted as B}from"../../aws/organisations/types.js";import{parseDockerDeclarationsFromManifest as G}from"@fjall/util/manifest";import{assertEngineCompatibleWithAssembly as K}from"./engineCompat.js";async function b(o,r,h,e,C){e.onDetectionPhaseChange?.("detect","started");const y=r.frameworkRegistry.resolve({appPath:o.path}),w=y?.detection.pattern??null,A=y?.detection.hasDatabase??!1;e.onLog?.(`Pattern detected: ${w??"standard"}`,"info"),e.onDetectionPhaseChange?.("detect","completed");const a=H(o.path,"cdk.out");e.onDetectionPhaseChange?.("synth","started"),R(e,E.SYNTH);const D=await r.cdkService.runCdkSynth(h,t=>e.onCdkOutput?.(t,"synth"));if(!D.success)return s(new Error(n(`CDK synthesis failed: ${D.error}`)));if(e.onDetectionPhaseChange?.("synth","completed"),h.resolvedSecretArns===void 0){const t=T(a);if(t.length>0){e.onLog?.(`Resolving ${t.length} imported secret ARN(s)\u2026`,"info");const i=await W(r.awsProvider,t);if(!i.success)return s(new Error(n(i.error.message)));h.resolvedSecretArns=JSON.stringify(i.data);const g=await r.cdkService.runCdkSynth(h,N=>e.onCdkOutput?.(N,"synth"));if(!g.success)return s(new Error(n(`CDK synthesis failed: ${g.error}`)))}}const P=K({cdkOutPath:a,onWarning:S(t=>e.onLog?.(t,"warn"),"onWarning")});if(!P.success)return s(new Error(n(P.error.message)));const m=q(a);m.length>0&&e.onLog?.(`Found ${m.length} required secret path(s) in manifest`,"info"),e.onDetectionPhaseChange?.("hash","started"),R(e,E.HASH);const d=await r.hashService.getTemplateHashes(a);if(!d.success)return s(new Error(n(`Template hash computation failed: ${d.error.message}`)));const u=d.data,f=await r.hashService.compareWithState(u,o.path);if(!f.success)return s(new Error(n(`Hash comparison failed: ${f.error.message}`)));const c=f.data;e.onDetectionPhaseChange?.("hash","completed");const p=new Map(c.stackChanges);for(const[t,i]of c.stackChanges){if(B(C))break;i||await r.cfnService.stackExists(t,void 0,C)||(O.debug("detectionPipeline","Stale hash detected \u2014 stack missing in CFN",{stackName:t}),p.set(t,!0))}const k=Array.from(u.keys()),l=L(k),v=l.hasCompute&&G(a).declaresBuild,$=Array.from(p.values()).some(Boolean);return e.onLog?.(`Detection complete: ${c.changedCount} changed, ${c.unchangedCount} unchanged`,"info"),M({pattern:w,hasDatabase:A,hasDifferences:$,stackChanges:p,currentHashes:u,resources:l,hasDockerfile:v,requiredSecrets:m})}S(b,"runDetectionPipeline");export{b as runDetectionPipeline};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deploy-time engine ↔ assembly compatibility gate.
|
|
3
|
+
*
|
|
4
|
+
* The orchestrating fjall engine (deploy-core) and the app's pinned
|
|
5
|
+
* `@fjall/components-infrastructure` are chosen by two independent mechanisms
|
|
6
|
+
* with no compatibility check. When they skew across a major, the failure is
|
|
7
|
+
* silent-wrong (dropped services, misconfigured images, IAM drift), not loud.
|
|
8
|
+
*
|
|
9
|
+
* This module is the ONE chokepoint that closes that gap: the constructs stamp
|
|
10
|
+
* an `engineCompat` constraint into the cloud assembly at synth (Phase 1); the
|
|
11
|
+
* deploy engine reads it here — after the final synth, before any build / push
|
|
12
|
+
* / CloudFormation call — and refuses to proceed on an incompatible pairing.
|
|
13
|
+
*
|
|
14
|
+
* Phase 0b (this increment) ships the READER only: `evaluateEngineCompat` and
|
|
15
|
+
* `assertEngineCompatibleWithAssembly` are live and wired into the deploy path,
|
|
16
|
+
* but NO emitter stamps `engineCompat` yet, so every real assembly classifies
|
|
17
|
+
* `no-constraint` and proceeds silently. This is deliberate: reader tolerance
|
|
18
|
+
* must roll out to every engine and worker BEFORE any emitter can stamp a
|
|
19
|
+
* constraint (the load-bearing ordering invariant of the rollout).
|
|
20
|
+
*
|
|
21
|
+
* Graduated, classify-don't-throw policy (`VERDICT_POLICY`):
|
|
22
|
+
* - engine-too-old / shape-too-new / malformed / indeterminate → REFUSE
|
|
23
|
+
* - engine-ahead (newer than the assembly's declared maximum major) → WARN, proceed
|
|
24
|
+
* - ok / no-constraint → proceed silently
|
|
25
|
+
* A refuse is overridable (audit-logged) via the `allowEngineSkew` parameter or
|
|
26
|
+
* the `FJALL_ALLOW_ENGINE_SKEW` env var — an escape hatch for an operator who
|
|
27
|
+
* knows better than the constraint, never a silent bypass.
|
|
28
|
+
*/
|
|
29
|
+
import { type Result } from "@fjall/generator";
|
|
30
|
+
import { type EngineCompat, type EngineCompatEnvelope } from "@fjall/util/manifest";
|
|
31
|
+
/** The distinct outcomes of comparing a running engine against an assembly. */
|
|
32
|
+
export type EngineCompatVerdictKind = "ok" | "no-constraint" | "engine-ahead" | "engine-too-old" | "shape-too-new" | "malformed-constraint" | "indeterminate";
|
|
33
|
+
export interface EngineCompatVerdict {
|
|
34
|
+
readonly kind: EngineCompatVerdictKind;
|
|
35
|
+
readonly message: string;
|
|
36
|
+
readonly engineCompat: EngineCompat | undefined;
|
|
37
|
+
readonly runningEngineVersion: string | undefined;
|
|
38
|
+
}
|
|
39
|
+
export interface EvaluateEngineCompatInput {
|
|
40
|
+
readonly envelope: EngineCompatEnvelope;
|
|
41
|
+
readonly runningEngineVersion: string | undefined;
|
|
42
|
+
readonly maxSupportedManifestVersion: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Pure classifier — takes a read envelope + the running engine version and
|
|
46
|
+
* returns a verdict, no I/O. The gate policy (refuse / warn / proceed) is
|
|
47
|
+
* applied by `assertEngineCompatibleWithAssembly`; this function only decides
|
|
48
|
+
* WHICH outcome the pairing is.
|
|
49
|
+
*/
|
|
50
|
+
export declare function evaluateEngineCompat(input: EvaluateEngineCompatInput): EngineCompatVerdict;
|
|
51
|
+
export interface AssertEngineCompatibleInput {
|
|
52
|
+
readonly cdkOutPath: string;
|
|
53
|
+
/** Defaults to `DEPLOY_CORE_VERSION` (the running engine). Overridable for tests. */
|
|
54
|
+
readonly runningEngineVersion?: string;
|
|
55
|
+
/** Explicit override — mirrors the `FJALL_ALLOW_ENGINE_SKEW` env escape hatch. */
|
|
56
|
+
readonly allowEngineSkew?: boolean;
|
|
57
|
+
/** Advisory-warning sink (WARN verdicts + audit-logged overrides). */
|
|
58
|
+
readonly onWarning?: (message: string) => void;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The deploy-time chokepoint. Reads the assembly's engine-compatibility
|
|
62
|
+
* envelope, classifies the pairing, and returns `failure` for a REFUSE verdict
|
|
63
|
+
* (unless overridden) so the caller short-circuits before any build/push/CFN
|
|
64
|
+
* mutation. WARN and PROCEED verdicts return `success`.
|
|
65
|
+
*
|
|
66
|
+
* Synchronous — the envelope read is `readFileSync` and the classifier is pure,
|
|
67
|
+
* matching the synchronous orchestration context of the deploy pipeline.
|
|
68
|
+
*
|
|
69
|
+
* The returned message carries only version identifiers and static text (never
|
|
70
|
+
* a credential value), so callers may surface it to a log / callback sink
|
|
71
|
+
* directly; the deploy pipeline still routes it through `maskSensitiveOutput`
|
|
72
|
+
* for consistency with its sibling failure paths.
|
|
73
|
+
*/
|
|
74
|
+
export declare function assertEngineCompatibleWithAssembly(input: AssertEngineCompatibleInput): Result<EngineCompatVerdict, Error>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var p=Object.defineProperty;var m=(r,i)=>p(r,"name",{value:i,configurable:!0});import{success as u,failure as E}from"@fjall/generator";import{logger as g}from"@fjall/util/logger";import{readEngineCompatEnvelope as y,MAX_SUPPORTED_MANIFEST_SCHEMA_VERSION as h}from"@fjall/util/manifest";import{DEPLOY_CORE_VERSION as b}from"../../version.js";const l="engineCompat",c="FJALL_ALLOW_ENGINE_SKEW",v={ok:"proceed","no-constraint":"proceed","engine-ahead":"warn","engine-too-old":"refuse","shape-too-new":"refuse","malformed-constraint":"refuse",indeterminate:"refuse"};function f(r){const i=r.trim().replace(/^v/i,"").split(/[-+]/)[0]??"";if(i.length===0)return;const e=i.split("."),o=Number(e[0]),n=e.length>1?Number(e[1]):0,s=e.length>2?Number(e[2]):0;if(!(!Number.isInteger(o)||o<0)&&!(!Number.isInteger(n)||n<0)&&!(!Number.isInteger(s)||s<0))return{major:o,minor:n,patch:s}}m(f,"parseEngineVersion");function V(r,i){return r.major!==i.major?r.major-i.major:r.minor!==i.minor?r.minor-i.minor:r.patch-i.patch}m(V,"compareEngineVersions");function a(r,i,e,o){return{kind:r,message:i,engineCompat:e,runningEngineVersion:o}}m(a,"verdict");function w(r){const{envelope:i,runningEngineVersion:e,maxSupportedManifestVersion:o}=r;if(i.manifestVersion!==void 0&&i.manifestVersion>o)return a("shape-too-new",`This assembly declares manifest schema version ${i.manifestVersion}, but this deploy engine (${e??"unknown"}) reads at most version ${o}. Upgrade fjall to deploy this assembly.`,i.engineCompat,e);if(i.engineCompatPresent&&i.engineCompat===void 0)return a("malformed-constraint","This assembly carries an engine-compatibility block with no usable `minimumEngineVersion`. Cannot verify the deploy engine is compatible.",void 0,e);const n=i.engineCompat;if(n===void 0)return a("no-constraint","Assembly declares no engine-compatibility constraint.",void 0,e);const s=f(n.minimumEngineVersion);if(s===void 0)return a("malformed-constraint",`This assembly declares an unparseable minimum engine version ("${n.minimumEngineVersion}"). Cannot verify compatibility.`,n,e);const t=e===void 0?void 0:f(e);return t===void 0?a("indeterminate",`This assembly requires deploy engine >= ${n.minimumEngineVersion}, but the running engine version could not be determined. Cannot verify compatibility.`,n,e):V(t,s)<0?a("engine-too-old",`This assembly was synthesised by @fjall/components-infrastructure ${n.synthesisedBy||"(unknown version)"} and requires deploy engine >= ${n.minimumEngineVersion}, but the running engine is ${e}. Upgrade the fjall engine to at least ${n.minimumEngineVersion} to deploy this assembly.`,n,e):n.maximumEngineMajor!==void 0&&t.major>n.maximumEngineMajor?a("engine-ahead",`The running deploy engine (${e}) is a newer major than the maximum (${n.maximumEngineMajor}) this assembly was validated against. Proceeding \u2014 re-verify the deploy if you see unexpected infrastructure changes.`,n,e):a("ok",`Deploy engine ${e} satisfies the assembly's minimum (${n.minimumEngineVersion}).`,n,e)}m(w,"evaluateEngineCompat");function C(){const r=process.env[c];return r!==void 0&&r!==""}m(C,"engineSkewOverrideFromEnv");function I(r){const{cdkOutPath:i,runningEngineVersion:e=b,allowEngineSkew:o,onWarning:n}=r,s=y(i),t=w({envelope:s,runningEngineVersion:e,maxSupportedManifestVersion:h}),d=v[t.kind];return d==="proceed"?u(t):d==="warn"?(g.warn(l,"Engine/assembly compatibility warning",{kind:t.kind,detail:t.message}),n?.(t.message),u(t)):o===!0||C()?(g.warn(l,"Engine/assembly compatibility gate OVERRIDDEN",{kind:t.kind,detail:t.message,via:o===!0?"parameter":c}),n?.(`Engine compatibility gate overridden \u2014 ${t.message}`),u(t)):E(new Error(t.message))}m(I,"assertEngineCompatibleWithAssembly");export{I as assertEngineCompatibleWithAssembly,w as evaluateEngineCompat};
|
|
@@ -32,6 +32,18 @@ export declare class CdkService {
|
|
|
32
32
|
* instead of the child's false failure.
|
|
33
33
|
*/
|
|
34
34
|
private reattachToInFlightOperation;
|
|
35
|
+
/**
|
|
36
|
+
* A zero-exit `cdk deploy` does not prove the stack converged: a transient
|
|
37
|
+
* network kill — or the retry that follows it — can return success while
|
|
38
|
+
* CloudFormation has independently settled at a ROLLBACK/FAILED terminal
|
|
39
|
+
* state. Before reporting a genuine deployment success, probe CloudFormation's
|
|
40
|
+
* authoritative terminal status and downgrade to failure when the stack rolled
|
|
41
|
+
* back. NO_CHANGES and skipped-empty-stack outcomes did not mutate the stack,
|
|
42
|
+
* so their terminal label reflects prior state and passes through untouched; a
|
|
43
|
+
* null probe (stack absent / API error) is not positive rollback evidence and
|
|
44
|
+
* preserves the CDK verdict.
|
|
45
|
+
*/
|
|
46
|
+
private reconcileDeploySuccessAgainstStack;
|
|
35
47
|
/** Shared by the Q12 merge path and the re-attach failure path. */
|
|
36
48
|
private journalDriftSuspects;
|
|
37
49
|
runCdkDestroy(context: DeploymentContext, stackPattern?: string, onOutput?: (chunk: string) => void, onResourceProgress?: (event: ResourceEvent) => void, aws?: AwsProvider, useCdkOut?: boolean, credentials?: CdkOptions["credentials"]): Promise<Result<StepOutput, string>>;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
var
|
|
2
|
-
${
|
|
3
|
-
`),T.warn("CdkService","Infrastructure typecheck failed",{path:
|
|
1
|
+
var q=Object.defineProperty;var M=(P,t)=>q(P,"name",{value:t,configurable:!0});import{existsSync as H}from"fs";import{join as K}from"path";import{logger as T}from"@fjall/util/logger";import{isSuccessState as L,isTerminalState as W}from"../../aws/utils/cloudformationEventHelpers.js";import{success as y,failure as s}from"@fjall/generator";import{DEFAULT_REGION as k}from"../../aws/utils/regions.js";import{getErrorMessage as b,maskSensitiveOutput as p}from"@fjall/util";import{CdkEventMonitor as X,DEFAULT_DEPLOY_TIMEOUT_MS as z,settleMonitoringAnalysis as j,startStackMonitoring as J}from"./CdkEventMonitoring.js";import{analyseDeployOutput as Q,analyseDestroyResult as V,createEnhancedOutputCallback as Z,formatAnalysedDeployFailure as $}from"./CdkOutputAnalyser.js";import{recordDriftSuspects as x}from"../../orchestration/drift/driftSuspectJournal.js";import{CdkCommandRunner as tt}from"./CdkCommandRunner.js";import{CdkArgumentBuilder as et}from"./CdkArgumentBuilder.js";import{CdkProcessManager as rt}from"./CdkProcessManager.js";import{cancelInFlightStack as U}from"./cancelInFlightStack.js";import{isAborted as A}from"../../aws/organisations/types.js";import{wrapWithConstructMapEnrichment as nt}from"./constructMapEnrichment.js";import{STACK_DETECTION_FALLBACK_MS as at,resolveStackName as G,getFallbackStackName as ot,buildDeploymentCdkContext as N}from"./cdkServiceHelpers.js";import{ensureDependencyIntegrity as it}from"./dependencyIntegrityGate.js";import{runInfraTypecheckPreflight as st}from"./infraTypecheckPreflight.js";import{isTransientNetworkFailure as ct,isReattachableInProgressStatus as ut,MAX_TRANSIENT_NETWORK_RETRIES as E,TRANSIENT_RETRY_BACKOFF_MS as lt}from"./transientNetworkRecovery.js";import{sleepAbortable as dt}from"../../util/sleepAbortable.js";class Pt{static{M(this,"CdkService")}commandRunner;eventMonitor;abortSignal;constructor(t){const e=t?.processManager??new rt(new et,t?.abortSignal);this.commandRunner=new tt(e),this.eventMonitor=new X({eventLogWriterFactory:t?.eventLogWriterFactory,...t?.onFailureAnalysis!==void 0?{onFailureAnalysis:t.onFailureAnalysis}:{}}),this.abortSignal=t?.abortSignal}dispose(){this.commandRunner.dispose()}async checkDifferences(t,e,r){return this.commandRunner.checkDifferences(t,e,r)}async deploy(t,e,r){return this.commandRunner.deploy(t,e,r)}async destroy(t,e,r){return this.commandRunner.destroy(t,e,r)}async runImport(t,e,r){return this.commandRunner.runImport(t,e,r)}async synth(t,e){return this.commandRunner.synth(t,e)}async bootstrap(t,e,r){return this.commandRunner.bootstrap(t,e,r)}async runCdkSynth(t,e,r){const a=t.callerIdentity?.Account;try{const n=await it(t.path,{...e!==void 0?{onOutput:e}:{},...this.abortSignal!==void 0?{abortSignal:this.abortSignal}:{}});if(!n.success)return s(n.error);const o=await st(t.path,{...this.abortSignal!==void 0?{abortSignal:this.abortSignal}:{}});o.status==="failed"&&(e?.(`WARNING: TypeScript errors in ${t.path} \u2014 the synthesised template may be wrong. This check will block deploys in a future release.
|
|
2
|
+
${o.details}
|
|
3
|
+
`),T.warn("CdkService","Infrastructure typecheck failed",{path:t.path}));const i=await this.synth(t.path,{outputCallback:e,context:N(t,a,t.region||k),...t.assemblyDir!==void 0?{outputDir:t.assemblyDir}:{},...r!==void 0?{credentials:r}:{}});return i.success?y({message:"CloudFormation template synthesised",details:i.data.output?{synthesisTime:i.data.output}:void 0}):s(i.error||"Failed to synthesise CloudFormation template")}catch(n){return s(`CDK synth failed: ${p(b(n))}`)}}async runCdkBootstrap(t,e,r,a,n){const o=t.callerIdentity?.Account,i=t.region||k;try{if(!o)return s("No AWS account ID available");const l=K(t.path,"node_modules");if(!H(l))return s(`Dependencies not installed. Please run 'npm install' in ${t.path} before deploying.`);const d=await this.bootstrap(o,i,{outputCallback:e,credentials:r,...a!==void 0&&a!==""?{permissionsBoundary:a}:{},...n!==void 0&&Object.keys(n).length>0?{bootstrapTags:n}:{}});return d.success?y({message:"AWS environment bootstrapped"}):s(d.error||"Failed to bootstrap AWS environment")}catch(l){return s(`CDK bootstrap failed: ${p(b(l))}`)}}async runCdkDiff(t,e){const r=t.callerIdentity?.Account;try{const a=await this.checkDifferences(t.path,void 0,{verbose:t.options?.verbose,outputCallback:e,context:N(t,r,t.region||k)});return a.success?y({message:"Diff check complete",details:{hasDifferences:a.data.hasDifferences,details:a.data.details}}):s(`CDK diff failed: ${a.error.message}`)}catch(a){return s(`CDK diff failed: ${p(b(a))}`)}}async runCdkDeploy(t,e,r,a,n,o,i,l){const d=t.callerIdentity?.Account,u=t.region||k;if(!d)return s("AWS account ID not available. Please ensure AWS credentials are properly configured.");if(!n)return s("AwsProvider is required for deployment monitoring.");const g=t.assemblyDir??K(t.path,"cdk.out"),S=nt(g,a);let h=null,D;try{const m=G(e,t)??ot(t),f=await this.eventMonitor.createEventMonitor("deploy",m,u,t,n);h=f,r&&(r(p(`Starting CloudFormation deployment of ${m}...
|
|
4
4
|
`)),r(`Monitoring CloudFormation events (CDK process running in background)...
|
|
5
|
-
`));const c={cdkOutput:"",actualStackName:
|
|
6
|
-
`);if(
|
|
7
|
-
`);const
|
|
8
|
-
`),await
|
|
9
|
-
|
|
10
|
-
`)
|
|
11
|
-
`),
|
|
5
|
+
`));const c={cdkOutput:"",actualStackName:m,stackDetected:!1,monitoringPromise:null},B=Z(c,r,h,S);D=setTimeout(()=>{!c.stackDetected&&!c.monitoringPromise&&(T.debug("CdkService","Fallback monitoring STARTING",{targetStackName:m,stackDetected:c.stackDetected,hasOnResourceProgress:!!a}),c.monitoringPromise=J(f,m,S))},at);for(let C=0;;C++){const R=await this.deploy(t.path,m,{verbose:t.options?.verbose,outputCallback:B,...t.assemblyDir!==void 0?{appDir:t.assemblyDir}:{useCdkOut:!0},cdkOutputLogger:f.getEventLogger()??void 0,context:N(t,d,u),credentials:o,...i!==void 0&&Object.keys(i).length>0&&{parameters:i},...l===!0&&{exclusively:!0}});if(A(this.abortSignal))return await U(m,u,o,r),s("Deployment cancelled");const w=Q(c.cdkOutput,R,c.actualStackName);if(w.success)return await this.reconcileDeploySuccessAgainstStack({successOutput:w.data,stackName:c.actualStackName,monitor:f,monitoringPromise:c.monitoringPromise,accountId:d,region:u,onOutput:r});const Y=[c.cdkOutput,R.success?R.data.output??"":R.error,w.error].join(`
|
|
6
|
+
`);if(ct(Y)){r?.(`Transient network failure while running CDK for ${c.actualStackName} \u2014 probing CloudFormation for an in-flight operation...
|
|
7
|
+
`);const I=await f.getStackStatus(c.actualStackName,this.abortSignal);if(I!==null&&ut(I.status))return clearTimeout(D),await this.reattachToInFlightOperation({stackName:c.actualStackName,currentStatus:I.status,region:u,accountId:d,monitor:f,state:c,credentials:o,onOutput:r,onResourceProgress:S});const v=I?.status,O=v!==void 0&&W(v)&&!L(v);if(!O&&C<E){const _=lt[C]??15e3;if(r?.(`No CloudFormation operation in progress \u2014 nothing was mutated. Retrying the CDK deploy in ${_/1e3}s (retry ${C+1} of ${E})...
|
|
8
|
+
`),await dt(_,this.abortSignal),A(this.abortSignal))return s("Deployment cancelled");c.cdkOutput="";continue}r?.(O?`CloudFormation settled at ${v} for ${c.actualStackName} \u2014 the stack was rolled back, not cleanly interrupted. Not retrying.
|
|
9
|
+
`:`No CloudFormation operation in progress and the retry budget (${E}) is exhausted \u2014 giving up.
|
|
10
|
+
`)}const F=await j(f,c.monitoringPromise);return F===null?w:(await this.journalDriftSuspects(d,u,c.actualStackName,F),s($(F)))}}catch(m){const f=`CDK deploy failed: ${p(b(m))}`;return T.error("CdkService","CDK deployment exception",{error:f}),s(f)}finally{clearTimeout(D),h&&h.stopMonitoring()}}async reattachToInFlightOperation(t){const{stackName:e,monitor:r,state:a,onOutput:n}=t;n?.(`CloudFormation reports ${t.currentStatus} for ${e} \u2014 the operation survived the network failure. Re-attaching and monitoring it to completion...
|
|
11
|
+
`),r.stopMonitoring(),a.monitoringPromise=null;const o=M(()=>{r.stopMonitoring()},"onAbort");this.abortSignal?.addEventListener("abort",o,{once:!0});const i=await r.waitForStackComplete(e,{timeout:z,...t.onResourceProgress!==void 0?{onResourceUpdate:t.onResourceProgress}:{}}).finally(()=>{this.abortSignal?.removeEventListener("abort",o)});if(A(this.abortSignal))return await U(e,t.region,t.credentials,n),s("Deployment cancelled");if(i.success){const u=i.status??"COMPLETE";return n?.(`${e} reached ${u} after re-attach.
|
|
12
|
+
`),y({message:`Re-attached to the in-flight CloudFormation operation after a transient network failure; ${e} reached ${u}`,details:{reattached:!0,status:u}})}const l=r.getFailureAnalysis();if(l!==null)return await this.journalDriftSuspects(t.accountId,t.region,e,l),s($(l));const d=i.failureReason!==void 0&&i.failureReason!==""?p(i.failureReason):"no failure reason reported";return s(`Re-attached to the in-flight CloudFormation operation after a transient network failure; ${e} reached ${i.status??"UNKNOWN"}: ${d}`)}async reconcileDeploySuccessAgainstStack(t){const{successOutput:e,stackName:r,monitor:a,onOutput:n}=t;if(e.status==="NO_CHANGES"||e.status==="SKIPPED"||e.details?.skipped===!0)return y(e);const o=await a.getStackStatus(r,this.abortSignal);if(o===null||!W(o.status)||L(o.status))return y(e);n?.(`CDK reported success but CloudFormation settled at ${o.status} for ${r} \u2014 the deployment did not converge. Reporting failure.
|
|
13
|
+
`);const i=await j(a,t.monitoringPromise);if(i!==null)return await this.journalDriftSuspects(t.accountId,t.region,r,i),s($(i));const l=o.statusReason!==void 0&&o.statusReason!==""?p(o.statusReason):"no failure reason reported";return s(`Deployment of ${r} reported success but CloudFormation settled at ${o.status}: ${l}`)}async journalDriftSuspects(t,e,r,a){a.driftSuspects===void 0||a.driftSuspects.length===0||await x({accountId:t,region:e,stackName:r,recordedAt:new Date().toISOString(),suspects:a.driftSuspects.map(n=>({stackName:r,logicalId:n.logicalId,resourceType:n.resourceType,...n.physicalId!==void 0?{physicalId:n.physicalId}:{},failedStatus:n.failedStatus,statusReason:n.statusReason}))})}async runCdkDestroy(t,e,r,a,n,o,i){const l=t.callerIdentity?.Account,d=t.region||k;let u=null;try{const g=G(e,t);l&&g&&n&&(u=await this.eventMonitor.createEventMonitor("destroy",g,d,t,n),u.startMonitoring(g,h=>{a?.(h)},(h,D)=>{}));const S=await this.destroy(t.path,e,{verbose:t.options?.verbose,outputCallback:r,useCdkOut:o,cdkOutputLogger:u?.getEventLogger()??void 0,context:N(t,l,d),credentials:i});return A(this.abortSignal)?s("Destroy cancelled"):V(S)}catch(g){return s(`CDK destroy failed: ${p(b(g))}`)}finally{u&&u.stopMonitoring()}}}export{Pt as CdkService};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const DEPLOY_CORE_VERSION: string | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var s=Object.defineProperty;var t=(r,o)=>s(r,"name",{value:o,configurable:!0});import{fileURLToPath as f}from"url";import{dirname as i,join as a,parse as m}from"path";import{existsSync as p,readFileSync as c}from"fs";import{logger as l}from"@fjall/util/logger";import{maskSensitiveOutput as d}from"@fjall/util";const u="@fjall/deploy-core";function g(){try{let r=i(f(import.meta.url));const{root:o}=m(r);for(;;){const n=a(r,"package.json");if(p(n)){const e=JSON.parse(c(n,"utf8"));if(e.name===u&&typeof e.version=="string"&&e.version.length>0)return e.version}if(r===o)return;r=i(r)}}catch(r){l.debug("deploy-core","Could not resolve own version",{error:d(r instanceof Error?r.message:String(r))});return}}t(g,"resolveDeployCoreVersion");const h=g();export{h as DEPLOY_CORE_VERSION};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fjall/deploy-core",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "Shared deployment engine for Fjall — used by CLI and webapp worker",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -88,8 +88,8 @@
|
|
|
88
88
|
"@aws-sdk/client-ssm": "^3.1038.0",
|
|
89
89
|
"@aws-sdk/client-sso-admin": "^3.1038.0",
|
|
90
90
|
"@aws-sdk/client-sts": "^3.1038.0",
|
|
91
|
-
"@fjall/generator": "^4.
|
|
92
|
-
"@fjall/util": "^4.
|
|
91
|
+
"@fjall/generator": "^4.2.0",
|
|
92
|
+
"@fjall/util": "^4.2.0",
|
|
93
93
|
"@smithy/node-http-handler": "^4.6.1",
|
|
94
94
|
"aws-cdk": "^2.1128.1",
|
|
95
95
|
"tsx": "^4.21.0",
|
|
@@ -100,5 +100,5 @@
|
|
|
100
100
|
"typescript": "^6.0.3",
|
|
101
101
|
"vitest": "^4.1.5"
|
|
102
102
|
},
|
|
103
|
-
"gitHead": "
|
|
103
|
+
"gitHead": "c06f7ad6b4b63a99cc8e2fcf4f216ff1c14748c6"
|
|
104
104
|
}
|