@fjall/deploy-core 2.33.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.minified +1 -1
- package/dist/src/aws/SimpleAwsProvider.js +1 -1
- package/dist/src/aws/cloudtrail/orgTrailDelivery.js +1 -1
- package/dist/src/aws/organisations/accountGlobals.js +1 -1
- package/dist/src/aws/organisations/accounts.js +1 -1
- package/dist/src/aws/organisations/backup.js +2 -2
- package/dist/src/aws/organisations/costAllocation.js +1 -1
- package/dist/src/aws/organisations/delegatedAdmin.js +2 -2
- package/dist/src/aws/organisations/identityCentre.js +1 -1
- package/dist/src/aws/organisations/identityCentreSnapshot.js +1 -1
- package/dist/src/aws/organisations/importedAccounts.js +1 -1
- package/dist/src/aws/organisations/ipam.js +1 -1
- package/dist/src/aws/organisations/oidcProvider.js +1 -1
- package/dist/src/aws/organisations/organisation.js +1 -1
- package/dist/src/aws/organisations/organisationalUnits.js +1 -1
- package/dist/src/aws/organisations/pagination.js +1 -1
- package/dist/src/aws/organisations/policies.js +1 -1
- package/dist/src/aws/organisations/ram.js +1 -1
- package/dist/src/aws/organisations/rootAccess.js +3 -3
- package/dist/src/aws/organisations/serviceAccess.js +1 -1
- package/dist/src/aws/organisations/trustedAccess.js +1 -1
- package/dist/src/aws/organisations/types.js +1 -1
- package/dist/src/aws/regions/regionOptStatus.js +1 -1
- package/dist/src/aws/sts/assumeRoot.js +1 -1
- package/dist/src/aws/targetReadiness.js +1 -1
- package/dist/src/aws/targetSetAdvisory.js +1 -1
- package/dist/src/aws/utils/arnParser.js +1 -1
- package/dist/src/aws/utils/awsErrorHandler.js +1 -1
- package/dist/src/aws/utils/cloudformationEventHelpers.js +1 -1
- package/dist/src/aws/utils/cloudformationEvents.js +1 -1
- package/dist/src/index.d.ts +4 -2
- package/dist/src/index.js +1 -1
- package/dist/src/orchestration/activeDeploymentGuard.js +5 -5
- package/dist/src/orchestration/application/applicationDeploy.js +1 -1
- package/dist/src/orchestration/application/applicationDeployHelpers.js +3 -3
- package/dist/src/orchestration/application/applicationDestroy.js +1 -1
- package/dist/src/orchestration/application/approvalGate.js +1 -1
- package/dist/src/orchestration/application/artefactCollection.js +1 -1
- package/dist/src/orchestration/application/buildArgResolver.js +1 -1
- package/dist/src/orchestration/application/buildGroupValidator.js +1 -1
- package/dist/src/orchestration/application/buildSecretResolver.js +1 -1
- package/dist/src/orchestration/application/buildSecretSession.js +1 -1
- package/dist/src/orchestration/application/codeOnlyDeploy.js +1 -1
- package/dist/src/orchestration/application/databaseEndpointReconcile.js +1 -1
- package/dist/src/orchestration/application/deploySessionPolicy.js +1 -1
- package/dist/src/orchestration/application/destroyPreFlight.js +1 -1
- package/dist/src/orchestration/application/destructionGate.js +3 -3
- package/dist/src/orchestration/application/detectionPipeline.js +1 -1
- package/dist/src/orchestration/application/dockerBuildHelper.js +1 -1
- package/dist/src/orchestration/application/ecrCacheRepository.js +1 -1
- package/dist/src/orchestration/application/lambdaCodeOnlyRollout.js +1 -1
- package/dist/src/orchestration/application/leaseGate.js +1 -1
- package/dist/src/orchestration/application/openNextBuild.js +3 -3
- package/dist/src/orchestration/application/plan/approvalToken.js +1 -1
- package/dist/src/orchestration/application/plan/assemblyDigest.js +2 -2
- package/dist/src/orchestration/application/plan/buildDeployPlan.js +1 -1
- package/dist/src/orchestration/application/plan/changeSetEscalation.js +1 -1
- package/dist/src/orchestration/application/plan/classify.js +1 -1
- package/dist/src/orchestration/application/plan/computeDeployPlan.js +1 -1
- package/dist/src/orchestration/application/plan/loadDeployPlan.js +1 -1
- package/dist/src/orchestration/application/plan/pinnedNames.js +1 -1
- package/dist/src/orchestration/application/plan/registryOverlay.js +1 -1
- package/dist/src/orchestration/application/plan/renderDeployPlan.js +1 -1
- package/dist/src/orchestration/application/serviceImageTagsRollback.js +1 -1
- package/dist/src/orchestration/application/taskDefinitionRoll.js +1 -1
- package/dist/src/orchestration/application/welcomeImageHelper.js +1 -1
- package/dist/src/orchestration/builders/dockerBuilder.js +1 -1
- package/dist/src/orchestration/builders/frameworkRegistry.js +1 -1
- package/dist/src/orchestration/builders/openNextBuilder.js +1 -1
- package/dist/src/orchestration/builders/staticSiteBuilder.js +2 -2
- package/dist/src/orchestration/contextHelpers.js +1 -1
- package/dist/src/orchestration/deploy.js +1 -1
- package/dist/src/orchestration/destroy.js +1 -1
- package/dist/src/orchestration/dockerBuildParamsKeys.js +1 -1
- package/dist/src/orchestration/dockerInterface.d.ts +16 -0
- package/dist/src/orchestration/domain/caaPreflight.d.ts +56 -0
- package/dist/src/orchestration/domain/caaPreflight.js +1 -0
- package/dist/src/orchestration/domain/delegatedDomainDeploy.d.ts +79 -0
- package/dist/src/orchestration/domain/delegatedDomainDeploy.js +1 -0
- package/dist/src/orchestration/domain/delegationDestroyMirror.d.ts +141 -0
- package/dist/src/orchestration/domain/delegationDestroyMirror.js +1 -0
- package/dist/src/orchestration/domain/index.d.ts +10 -0
- package/dist/src/orchestration/domain/index.js +1 -0
- package/dist/src/orchestration/domain/nsPropagationGate.d.ts +59 -0
- package/dist/src/orchestration/domain/nsPropagationGate.js +1 -0
- package/dist/src/orchestration/domain/types.d.ts +75 -0
- package/dist/src/orchestration/domain/types.js +1 -0
- package/dist/src/orchestration/domainInterface.d.ts +10 -0
- package/dist/src/orchestration/drift/classifyDriftFailure.js +1 -1
- package/dist/src/orchestration/drift/detectStackDrift.js +1 -1
- package/dist/src/orchestration/drift/driftPreFlight.js +5 -5
- package/dist/src/orchestration/drift/driftProbe.d.ts +12 -6
- package/dist/src/orchestration/drift/driftProbe.js +1 -1
- package/dist/src/orchestration/drift/driftSuspectJournal.js +1 -1
- package/dist/src/orchestration/drift/preFlightGate.d.ts +10 -2
- package/dist/src/orchestration/drift/preFlightGate.js +2 -2
- package/dist/src/orchestration/drift/route53Names.d.ts +35 -0
- package/dist/src/orchestration/drift/route53Names.js +1 -0
- package/dist/src/orchestration/drift/route53RecordPreflight.d.ts +105 -0
- package/dist/src/orchestration/drift/route53RecordPreflight.js +2 -0
- package/dist/src/orchestration/index.d.ts +3 -1
- package/dist/src/orchestration/index.js +1 -1
- package/dist/src/orchestration/manifestSecretParser.js +1 -1
- package/dist/src/orchestration/organisation/accountsConfig.js +1 -1
- package/dist/src/orchestration/organisation/cascadeDestroyHelpers.js +1 -1
- package/dist/src/orchestration/organisation/cascadeHelpers.d.ts +18 -1
- package/dist/src/orchestration/organisation/cascadeHelpers.js +1 -1
- package/dist/src/orchestration/organisation/cascadeSummary.js +1 -1
- package/dist/src/orchestration/organisation/organisationDeploy.js +1 -1
- package/dist/src/orchestration/organisation/organisationDestroy.js +2 -2
- package/dist/src/orchestration/organisation/organisationSetup.js +1 -1
- package/dist/src/orchestration/organisation/reconcileProviderAccounts.js +1 -1
- package/dist/src/orchestration/organisationDeploy/cascadeExecution.js +1 -1
- package/dist/src/orchestration/organisationDeploy/infraSteps.js +1 -1
- package/dist/src/orchestration/organisationDeploy/orgCascadeDeploy.js +3 -3
- package/dist/src/orchestration/organisationDeploy/orgContext.js +1 -1
- package/dist/src/orchestration/organisationDeploy/resolveCascadeAccounts.js +1 -1
- package/dist/src/orchestration/organisationDeploy/singleComponentDeploy.js +1 -1
- package/dist/src/orchestration/organisationDeploy/trailReconciliation.js +1 -1
- package/dist/src/orchestration/probes/deleteBlockProbes.js +1 -1
- package/dist/src/orchestration/remediation/driftRepairGate.js +1 -1
- package/dist/src/orchestration/remediation/forensicsCapture.js +1 -1
- package/dist/src/orchestration/remediation/forgetResource.js +1 -1
- package/dist/src/orchestration/remediation/pinRemediation.js +1 -1
- package/dist/src/orchestration/remediation/postFailureRepairOffer.js +8 -8
- package/dist/src/orchestration/remediation/recreatePreFlight.js +1 -1
- package/dist/src/orchestration/remediation/recreateResource.js +1 -1
- package/dist/src/orchestration/remediation/remediationJournal.js +2 -2
- package/dist/src/orchestration/remediation/removalUpdate.js +1 -1
- package/dist/src/orchestration/remediation/retainFlip.js +1 -1
- package/dist/src/orchestration/resolveOperation.js +1 -1
- package/dist/src/orchestration/restart/restartApplication.js +1 -1
- package/dist/src/orchestration/restart/secretsDrift.js +1 -1
- package/dist/src/orchestration/secretArnResolver.js +1 -1
- package/dist/src/orchestration/serviceFactory.js +1 -1
- package/dist/src/orchestration/spawnHelpers.js +1 -1
- package/dist/src/orchestration/stackCleanup/bucketOps.js +1 -1
- package/dist/src/orchestration/stackCleanup/failedStack.js +1 -1
- package/dist/src/orchestration/stackCleanup/logging.js +1 -1
- package/dist/src/orchestration/stackCleanup/messages.js +1 -1
- package/dist/src/orchestration/stackCleanup/orphanSweep.js +1 -1
- package/dist/src/orchestration/stackCleanup/preEmpty.js +1 -1
- package/dist/src/orchestration/stackCleanup/stackResources.js +1 -1
- package/dist/src/orchestration/stepLifecycle.js +1 -1
- package/dist/src/orchestration/trailMigration/memberTrailCleanup.js +1 -1
- package/dist/src/orchestration/trailMigration/trailMigration.js +1 -1
- package/dist/src/orchestration/unlock/bucketPolicyTriage.js +1 -1
- package/dist/src/orchestration/unlock/restoreAndReconcileQuarantinedBucket.js +1 -1
- package/dist/src/orchestration/unlock/restoreBucketPolicy.js +1 -1
- package/dist/src/orchestration/unlock/scpRemediation.js +1 -1
- package/dist/src/orchestration/unlock/toResourcePolicyStatements.js +1 -1
- package/dist/src/orchestration/unlock/unlockBucket.js +1 -1
- package/dist/src/orchestration/unlock/unlockQueue.js +1 -1
- package/dist/src/services/application/ApplicationStackService.d.ts +7 -0
- package/dist/src/services/application/ApplicationStackService.js +1 -1
- package/dist/src/services/application/applicationStackHelpers.js +3 -3
- package/dist/src/services/infrastructure/CdkArgumentBuilder.js +1 -1
- package/dist/src/services/infrastructure/CdkCommandRunner.js +2 -2
- package/dist/src/services/infrastructure/CdkErrorFormatter.js +10 -10
- package/dist/src/services/infrastructure/CdkEventMonitoring.js +1 -1
- package/dist/src/services/infrastructure/CdkOutputAnalyser.js +2 -2
- package/dist/src/services/infrastructure/CdkOutputParser.js +2 -2
- package/dist/src/services/infrastructure/CdkProcessManager.js +3 -3
- package/dist/src/services/infrastructure/CdkService.d.ts +1 -1
- package/dist/src/services/infrastructure/CdkService.js +2 -2
- package/dist/src/services/infrastructure/CdkServiceTypes.d.ts +39 -0
- package/dist/src/services/infrastructure/CfnRegistryService.js +1 -1
- package/dist/src/services/infrastructure/CloudFormationService.js +1 -1
- package/dist/src/services/infrastructure/EcrImageInspectorService.js +1 -1
- package/dist/src/services/infrastructure/EcsService.js +1 -1
- package/dist/src/services/infrastructure/EcsServiceResolver.js +1 -1
- package/dist/src/services/infrastructure/LambdaService.js +1 -1
- package/dist/src/services/infrastructure/SsmParameterMetadataService.js +1 -1
- package/dist/src/services/infrastructure/cancelInFlightStack.js +3 -3
- package/dist/src/services/infrastructure/cdkServiceHelpers.js +1 -1
- package/dist/src/services/infrastructure/changeSetProbe.js +1 -1
- package/dist/src/services/infrastructure/constructMapEnrichment.js +1 -1
- package/dist/src/services/supporting/CdkContextBuilder.d.ts +1 -0
- package/dist/src/services/supporting/CdkContextBuilder.js +1 -1
- package/dist/src/services/supporting/TemplateHashService.js +1 -1
- package/dist/src/services/supporting/helpers.js +1 -1
- package/dist/src/steps/stepRegistry.js +1 -1
- package/dist/src/types/ProgressEvent.js +1 -1
- package/dist/src/types/application/ApplicationServiceTypes.js +1 -1
- package/dist/src/types/callbacks.js +1 -1
- package/dist/src/types/config/FjallState.js +1 -1
- package/dist/src/types/constants.js +1 -1
- package/dist/src/types/deployEmitter.js +1 -1
- package/dist/src/types/deployEvent.d.ts +1 -0
- package/dist/src/types/deployEvent.js +1 -1
- package/dist/src/types/deployEventParse.js +1 -1
- package/dist/src/types/deployment/DeploymentServiceTypes.js +1 -1
- package/dist/src/types/deployment/DeploymentTypes.d.ts +1 -0
- package/dist/src/types/deploymentEventSchema.d.ts +1 -0
- package/dist/src/types/deploymentEventSchema.js +1 -1
- package/dist/src/types/detection/patternDetection.js +1 -1
- package/dist/src/types/detection/patternTypes.d.ts +9 -0
- package/dist/src/types/drift.js +1 -1
- package/dist/src/types/errors/CdkError.js +1 -1
- package/dist/src/types/errors/ServiceError.js +1 -1
- package/dist/src/types/operations.d.ts +26 -2
- package/dist/src/types/operations.js +1 -1
- package/dist/src/types/params.d.ts +20 -0
- package/dist/src/types/remediation.js +1 -1
- package/dist/src/types/stepDefinitions.d.ts +3 -0
- package/dist/src/types/stepDefinitions.js +1 -1
- package/dist/src/util/dockerfileDetection.js +1 -1
- package/dist/src/util/sequencedCallbacks.js +1 -1
- package/dist/src/util/sleepAbortable.js +1 -1
- package/package.json +5 -4
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{CloudFormationClient as
|
|
1
|
+
var G=Object.defineProperty;var k=(o,e)=>G(o,"name",{value:e,configurable:!0});import{CloudFormationClient as x}from"@aws-sdk/client-cloudformation";import{S3Client as v}from"@aws-sdk/client-s3";import{IAMClient as Y}from"@aws-sdk/client-iam";import{success as M,failure as D}from"@fjall/generator";import{maskSensitiveOutput as E,getErrorMessage as F,mapSettledWithConcurrency as U}from"@fjall/util";import{stubCallerIdentity as j}from"../../types/deployment/index.js";import{ORGANISATION_TYPES as N,getOrganisationStackName as T}from"../../types/operations.js";import{CdkContextBuilder as H}from"../../services/supporting/CdkContextBuilder.js";import{buildParamsContext as W,synthOrFail as X,forwardOutput as q,forwardResourceProgress as z}from"../contextHelpers.js";import{destroyCascadeAccount as b}from"./cascadeDestroyHelpers.js";import{emitOrgStackTargets as J}from"../organisationDeploy/infraSteps.js";import{assertLeaseBeforeStack as K}from"../application/leaseGate.js";import{cleanupOrphanedOidcProvider as Q}from"../../aws/organisations/oidcProvider.js";import{capturedSweepBuckets as V,preEmptyStackBuckets as Z,sweepOrphanedDestroyBuckets as tt}from"../stackCleanup.js";import{partitionAccounts as ot,buildRegionList as et,buildAccountRegionPairs as B,cascadeHomeRegion as rt,CASCADE_MAX_CONCURRENCY as nt}from"./cascadeHelpers.js";import{projectScalarSummary as at}from"./cascadeSummary.js";import{STEP_IDS as it}from"../../types/stepDefinitions.js";const $=it.ORG_DESTROY,_="Destroying organisation infrastructure";async function Dt(o,e,u){const R=Date.now(),{callbacks:t}=o,p=o.orgConfig?.providerAccounts??[],d=rt(o.orgConfig),m=et(o.orgConfig),{platformAccount:n,memberAccounts:C}=ot(p),f=o.options?.cascade!==!1;t.onLog?.(`Destroying organisation infrastructure (${p.length} accounts, ${m.length} region(s))`,"info");const S=e.awsProvider.getAccountId(),y=e.awsProvider.getRegion();J(t,[{stackName:T(N.ORGANISATION),...S!==""?{accountId:S}:{},...y!==""?{region:y}:{}},...f&&n!==void 0?[{stackName:T(N.PLATFORM),accountId:n.id,region:n.region??d}]:[],...f?B(C,m).map(({account:P,region:l})=>({stackName:T(N.ACCOUNT),accountId:P.id,region:l})):[]]);const s=[],w=[],I=[];let A,h;if(f){t.onCascadeStart?.();const P=Date.now();if(C.length>0){t.onCascadePhaseStart?.("accounts");const a=B(C,m),g=await U(a,nt,({account:c,region:i})=>b(o,e,u,c,"account",i,t,{primaryRegion:d}));for(let c=0;c<g.length;c++){const i=g[c],r=a[c];if(!(!i||!r))if(i.status==="fulfilled")if(i.value.success)w.push(`Account-${r.account.name}-${r.region}`),I.push({accountId:r.account.id,accountName:r.account.name,region:r.region,result:"succeeded",durationMs:i.value.duration});else{const O=E(i.value.error??"Unknown error");I.push({accountId:r.account.id,accountName:r.account.name,region:r.region,result:"failed",durationMs:i.value.duration,error:O}),s.push({accountId:r.account.id,error:O}),h??=i.value.leaseDenied}else{const O=E(F(i.reason));I.push({accountId:r.account.id,accountName:r.account.name,region:r.region,result:"failed",durationMs:0,error:O}),s.push({accountId:r.account.id,error:O})}}t.onCascadePhaseComplete?.("accounts")}if(n){t.onCascadePhaseStart?.("platform");const a=await b(o,e,u,n,"platform",n.region??d,t);if(a.success)w.push("Platform"),A={accountId:n.id,result:"succeeded",durationMs:a.duration};else{const g=E(a.error??"Platform destroy failed");s.push({accountId:n.id,error:g}),h??=a.leaseDenied,A={accountId:n.id,result:"failed",durationMs:a.duration,error:g}}t.onCascadePhaseComplete?.("platform")}const l={members:I,...A!==void 0?{platform:A}:{},domainsDeployed:!1,errors:s,totalDurationMs:Date.now()-P};if(t.onCascadeComplete?.(at(l)),t.onCascadeLedger?.(l),s.length>0){const a=s.map(c=>` ${c.accountId}: ${c.error}`).join(`
|
|
2
2
|
`),g=new Error(`Cascade destroy completed with ${s.length} failure(s):
|
|
3
|
-
${a}`);t.onError?.(g),t.onLog?.(g.message,"warn")}}if(h!==void 0)return t.onError?.(h),D(h);if(s.length>0)return t.onLog?.("Skipping organisation root stack destroy due to cascade failures","warn"),
|
|
3
|
+
${a}`);t.onError?.(g),t.onLog?.(g.message,"warn")}}if(h!==void 0)return t.onError?.(h),D(h);if(s.length>0)return t.onLog?.("Skipping organisation root stack destroy due to cascade failures","warn"),M({target:u.target,deploymentType:"organisation",stacksDestroyed:w,durationMs:Date.now()-R,warnings:s.map(l=>`${l.accountId}: ${l.error}`)});t.onStepStart?.($,_,0,1);const L=await st(o,e,u,e.awsProvider.getRegion(),t);if(L.success)w.push("Organisation"),t.onStepComplete?.($,_,"completed",0,1);else return t.onStepComplete?.($,_,"error",0,1),D(L.error);return M({target:u.target,deploymentType:"organisation",stacksDestroyed:w,durationMs:Date.now()-R})}k(Dt,"destroyOrganisation");async function st(o,e,u,R,t){const p=H.buildDeploymentContext({deployType:"organisation",target:u.target,path:u.path,region:R,callerIdentity:j(e.awsProvider.getAccountId()),...W({orgConfig:o.orgConfig,identity:o.identity})},{verbose:o.options?.verbose},o.orgConfig),d=T(N.ORGANISATION);t.onLog?.("Synthesising organisation infrastructure\u2026","info");const m=await X(e,p,t,"Organisation synth failed");if(!m.success)return m;const n=await K(o.assertLeaseForStack,d,o.abortSignal,"destroy");if(n!==void 0)return t.onError?.(n),D(n);const C=await Z(e.awsProvider.getClient(x),e.awsProvider.getClient(v),d,t,o.abortSignal),f=V(C);t.onLog?.(`Destroying ${d} stack\u2026`,"info");const S=await e.cdkService.runCdkDestroy(p,d,q(t),z(t),e.awsProvider,!0);if(!S.success){const y=new Error(E(`Organisation destroy failed: ${S.error}`));return t.onError?.(y),D(y)}return f.length>0&&await tt(e.awsProvider.getClient(v),f,t,o.abortSignal),await Q(e.awsProvider.getClient(Y),t,o.abortSignal),M(void 0)}k(st,"destroyOrgRootStack");export{Dt as destroyOrganisation};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{success as
|
|
1
|
+
var $=Object.defineProperty;var p=(t,o)=>$(t,"name",{value:o,configurable:!0});import{success as w,failure as C}from"@fjall/generator";import{OrganizationsClient as B}from"@aws-sdk/client-organizations";import{RAMClient as G}from"@aws-sdk/client-ram";import{CloudFormationClient as _}from"@aws-sdk/client-cloudformation";import{EC2Client as z}from"@aws-sdk/client-ec2";import{IAMClient as H}from"@aws-sdk/client-iam";import{BackupClient as q}from"@aws-sdk/client-backup";import{CostExplorerClient as J}from"@aws-sdk/client-cost-explorer";import{SSOAdminClient as Q}from"@aws-sdk/client-sso-admin";import{ensureOrganisationExists as V}from"../../aws/organisations/organisation.js";import{enablePolicyTypes as X}from"../../aws/organisations/policies.js";import{enableServiceAccess as Y}from"../../aws/organisations/serviceAccess.js";import{enableRamSharing as Z}from"../../aws/organisations/ram.js";import{activateTrustedAccess as b}from"../../aws/organisations/trustedAccess.js";import{enableIpamDelegatedAdmin as k}from"../../aws/organisations/ipam.js";import{updateBackupGlobalSettings as ee}from"../../aws/organisations/backup.js";import{listAccounts as I,createAccount as te}from"../../aws/organisations/accounts.js";import{ensureOrganisationalUnitsExist as ne,placeAccountsInOUs as oe,buildAccountToOUMap as re}from"../../aws/organisations/organisationalUnits.js";import{activateCostAllocationTags as se}from"../../aws/organisations/costAllocation.js";import{checkIdentityCentreStatus as ie}from"../../aws/organisations/identityCentre.js";import{registerSecurityDelegates as ae}from"../../aws/organisations/delegatedAdmin.js";import{enableCentralisedRootAccess as ce}from"../../aws/organisations/rootAccess.js";import{selectImportedMemberAccounts as ue,formatImportedAccountsWarning as me}from"../../aws/organisations/importedAccounts.js";import{isOULeaf as de}from"../../aws/organisations/types.js";const Ge=["create-organisation","enable-policies","enable-service-access","enable-root-access","enable-ram-sharing","activate-trusted-access","enable-ipam","configure-backup","create-accounts","create-organisational-units","place-accounts","activate-cost-tags","check-identity-centre","register-security-delegates"];async function _e(t,o,e,r){const n=[],i=[],s=[],m=[];let a;const u=t.getClient(B),N=t.getClient(H),S=t.getClient(G),L=t.getClient(_),W=t.getClient(z),F=t.getClient(q),j=t.getClient(J),D=t.getClient(Q);e?.onPhaseStart?.("create-organisation"),e?.onProgress?.("Ensuring AWS Organisation exists");const l=await V(u,r);if(!l.success)return e?.onError?.("create-organisation",l.error),e?.onPhaseComplete?.("create-organisation","error"),C(l.error);const{orgId:K,rootId:E,managementAccountId:R}=l.data;if(e?.onPhaseComplete?.("create-organisation","completed"),n.push("create-organisation"),await d("enable-policies",()=>(e?.onProgress?.("Enabling organisation policy types"),X(u,E,{abortSignal:r})),n,s,e),await d("enable-service-access",()=>(e?.onProgress?.("Enabling AWS service access"),Y(u,r)),n,s,e),o.skipRootAccessManagement?g("enable-root-access",i,e):await d("enable-root-access",async()=>{e?.onProgress?.("Enabling centralised root access management");const c=await ce(N,r);return(c.success?c.data.enabled:c.error.partialSummary.enabled).length>0&&await pe(u,R,e,r),c},n,s,e),await d("enable-ram-sharing",()=>(e?.onProgress?.("Enabling RAM sharing"),Z(S,r)),n,s,e),await d("activate-trusted-access",()=>(e?.onProgress?.("Activating CloudFormation trusted access"),b(L,r)),n,s,e),o.platformAccountId){const c=o.platformAccountId;await d("enable-ipam",()=>(e?.onProgress?.("Enabling IPAM delegated administrator"),k(W,c,r)),n,s,e)}else g("enable-ipam",i,e);await d("configure-backup",()=>(e?.onProgress?.("Updating backup global settings"),ee(F,void 0,r)),n,s,e),e?.onPhaseStart?.("create-accounts"),e?.onProgress?.("Checking for missing accounts");const A=await ge(u,o.accounts,m,e,r);let O=[];A.success?(O=A.data,n.push("create-accounts"),e?.onPhaseComplete?.("create-accounts","completed")):P("create-accounts",A.error,s,e);let h={};e?.onPhaseStart?.("create-organisational-units"),e?.onProgress?.("Ensuring organisational units exist");const y=await ne(u,E,o.organisationalUnits,r);y.success?(h=y.data,n.push("create-organisational-units"),e?.onPhaseComplete?.("create-organisational-units","completed")):P("create-organisational-units",y.error,s,e);const f=Array.isArray(o.organisationalUnits)?void 0:o.organisationalUnits,v=o.accountPlacements??[],T=v.length===0&&f!==void 0?fe(f,O,R):v;if(Object.keys(h).length===0)g("place-accounts",i,e);else if(o.accountPlacements===void 0&&f===void 0){const c=new Error("Account placements not provided despite OUs being created. Caller must populate accountPlacements (flat-list mode cannot derive placements internally).");s.push({phase:"place-accounts",error:c.message}),e?.onPhaseStart?.("place-accounts"),e?.onError?.("place-accounts",c),e?.onPhaseComplete?.("place-accounts","error")}else if(T.length===0)g("place-accounts",i,e);else{const c=f?re(f,h):void 0;await d("place-accounts",()=>(e?.onProgress?.("Placing accounts in organisational units"),oe(u,h,T,c,r)),n,s,e)}const U=o.costAllocationTags??[];if(U.length>0?await d("activate-cost-tags",()=>(e?.onProgress?.("Activating cost allocation tags"),se(j,U.map(c=>({TagKey:c})),r)),n,s,e):g("activate-cost-tags",i,e),o.skipIdentityCentre)g("check-identity-centre",i,e);else{e?.onPhaseStart?.("check-identity-centre"),e?.onProgress?.("Checking Identity Centre status");const c=await ie(D,r);c.success?(a=c.data.enabled?"enabled":"not-enabled",n.push("check-identity-centre"),e?.onPhaseComplete?.("check-identity-centre","completed")):P("check-identity-centre",c.error,s,e)}const M=o.securityDelegateAccountId;return M?await d("register-security-delegates",()=>(e?.onProgress?.("Registering security service delegated administrators"),ae(u,M,void 0,r)),n,s,e):g("register-security-delegates",i,e),w({organisationId:K,createdAccounts:m,identityCentreStatus:a,phasesCompleted:n,phasesSkipped:i,errors:s})}p(_e,"runOrganisationSetup");function P(t,o,e,r){e.push({phase:t,error:o.message}),r?.onError?.(t,o),r?.onPhaseComplete?.(t,"error")}p(P,"recordPhaseError");function g(t,o,e){o.push(t),e?.onPhaseStart?.(t),e?.onPhaseComplete?.(t,"skipped")}p(g,"skipPhase");async function pe(t,o,e,r){const n=await I(t,r);if(!n.success){e?.onProgress?.(`Could not check for imported member accounts: ${n.error.message}`);return}const i=ue(n.data,o);if(i.length===0)return;const s=me(i);e?.onWarning?e.onWarning("enable-root-access",s):e?.onProgress?.(s)}p(pe,"warnOnImportedMemberAccounts");async function d(t,o,e,r,n){n?.onPhaseStart?.(t);const i=await o();i.success?(e.push(t),n?.onPhaseComplete?.(t,"completed")):P(t,i.error,r,n)}p(d,"executePhase");async function ge(t,o,e,r,n){const i=await I(t,n);if(!i.success)return C(i.error);const s=new Set(i.data.map(a=>a.Name?.toLowerCase()).filter(a=>a!==void 0));for(const a of o){if(s.has(a.name.toLowerCase()))continue;r?.onProgress?.(`Account "${a.name}" is not yet a member of this AWS Organisation. Fjall will create a new account (with a new account ID). If "${a.name}" already exists as a standalone Fjall-connected account, abort now and import it into the organisation instead, because creating here produces a duplicate account. See the account-import runbook.`);const u=await te(t,a.name,a.email,void 0,void 0,n);if(!u.success)return C(u.error);e.push({name:u.data.accountName,accountId:u.data.accountId})}if(e.length===0)return w(i.data);const m=await I(t,n);return m.success?w(m.data):C(m.error)}p(ge,"createMissingAccounts");function x(t){const o=[];for(const[e,r]of Object.entries(t))if(de(r))for(const n of r)o.push({accountName:n,placementKey:e});else o.push(...x(r));return o}p(x,"collectTreeAccountPlacements");function fe(t,o,e){const r=x(t),n=new Map;for(const s of o){const m=s.Name?.toLowerCase();m&&s.Id&&n.set(m,s)}const i=[];for(const{accountName:s,placementKey:m}of r){const a=n.get(s.toLowerCase());!a?.Id||!a.Name||e!==void 0&&a.Id===e||i.push({id:a.Id,name:a.Name,environment:m})}return i}p(fe,"derivePlacementsFromTree");export{Ge as ORG_SETUP_PHASES,_e as runOrganisationSetup};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{z as
|
|
1
|
+
var y=Object.defineProperty;var u=(t,i)=>y(t,"name",{value:i,configurable:!0});import{z as f}from"zod";import{success as E,failure as c}from"@fjall/generator";import{environmentToTier as g,stageFromWireEnvironment as P,normaliseError as w}from"@fjall/util";import{OrganizationsClient as S}from"@aws-sdk/client-organizations";import{listAccounts as h}from"../../aws/organisations/accounts.js";import{parseAccountsConfiguration as O,flattenAccountsToEnvironments as I}from"./accountsConfig.js";const C=6e4,M=f.object({Id:f.string().min(1),Name:f.string().min(1)});async function $(t,i){let n;const r=new Promise((o,e)=>{n=setTimeout(()=>e(new Error(`Timed out parsing ACCOUNTS configuration after ${C}ms`)),C)});let s;try{s=await Promise.race([O(i),r])}catch(o){return c(w(o))}finally{n!==void 0&&clearTimeout(n)}if(!s.success)return c(new Error(`Failed to parse ACCOUNTS configuration: ${s.error.message}`));if(s.data===null)return c(new Error("ACCOUNTS configuration file not found"));const N=I(s.data),d=new Map;for(const{accountName:o,environment:e}of N)d.set(o.toLowerCase(),{environment:e,displayName:o});let l;try{l=t.awsProvider.getClient(S)}catch(o){return c(w(o))}const a=await h(l);if(!a.success)return c(a.error);const p=new Map;for(const o of a.data){const e=M.safeParse(o);e.success&&p.set(e.data.Name.toLowerCase(),{id:e.data.Id,name:e.data.Name})}const A=[],v=[];for(const[o,{environment:e,displayName:T}]of d){const m=p.get(o);if(!m){v.push(T);continue}g(e)!=="organisation"&&A.push({id:m.id,name:m.name,tier:g(e),environment:P(e)})}return E({providerAccounts:A,missingAccountNames:v})}u($,"reconcileProviderAccounts");function j(t,i){const n=new Map;for(const r of t?.providerAccounts??[])n.set(r.id,r);for(const r of i)n.has(r.id)||n.set(r.id,r);return{...t??{},providerAccounts:Array.from(n.values())}}u(j,"mergeReconciledProviderAccounts");export{j as mergeReconciledProviderAccounts,$ as reconcileProviderAccounts};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{failure as
|
|
1
|
+
var j=Object.defineProperty;var b=(o,S)=>j(o,"name",{value:S,configurable:!0});import{failure as W}from"@fjall/generator";import{regionSuffix as $}from"../../types/config/FjallState.js";import{partitionAccounts as X,deployCascadeAccount as x,readPlatformIpamPoolIds as Y,deployDomains as z,buildRegionList as B,buildAccountRegionPairs as G,cascadeHomeRegion as J,CASCADE_MAX_CONCURRENCY as K}from"../organisation/cascadeHelpers.js";import{projectScalarSummary as T}from"../organisation/cascadeSummary.js";import{getErrorMessage as Q,maskSensitiveOutput as C,mapSettledWithConcurrency as V}from"@fjall/util";import{INFRA_STEPS as E}from"./infraSteps.js";async function ce(o,S,N,q){const{callbacks:e}=o,{providerAccounts:H,effectiveOrgConfig:R,totalSteps:p,cascadeErrors:l,allCascadeOutputs:M}=q;let g=!1;e.onCascadeStart?.();const U=Date.now();let d=2,y=!1,h,D=!1;const A=[],O=b(t=>({members:A,...h!==void 0?{platform:h}:{},domainsDeployed:D,errors:l,totalDurationMs:t}),"buildLedger"),{platformAccount:r,memberAccounts:I}=X(H),_=J(o.orgConfig);if(r){const{id:t,name:a}=E.CASCADE_PLATFORM;e.onStepStart?.(t,a,d,p),e.onCascadePhaseStart?.("platform");let c;const P=Date.now();try{c=await x(o,S,N,r,"platform",e,{orgConfig:R,primaryRegion:_})}catch(i){const n=C(Q(i));l.push({accountId:r.id,error:n}),e.onCascadePhaseComplete?.("platform"),e.onStepComplete?.(t,a,"error",d,p),c=W(new Error(n))}const w=Date.now()-P;if(c.success){y=!0;const i=c.data.skipped===!0;i||(g=!0),c.data.outputs&&M.push({accountId:r.id,outputs:c.data.outputs}),e.onCascadePhaseComplete?.("platform"),e.onStepComplete?.(t,a,i?"skipped":"completed",d,p),h={accountId:r.id,result:i?"skipped":"succeeded",durationMs:w}}else l.some(i=>i.accountId===r.id)||(l.push({accountId:r.id,error:C(c.error.message)}),e.onCascadePhaseComplete?.("platform"),e.onStepComplete?.(t,a,"error",d,p)),h={accountId:r.id,result:"failed",durationMs:w,error:C(c.error.message)};d++}let F=new Map;if(y&&r&&(F=await Y(S,r,e,o.abortSignal)),o.domainProvider){const t=await z(o.domainProvider,e,{...o.abortSignal!==void 0?{abortSignal:o.abortSignal}:{},...o.options?.skipCaaPreflight!==void 0?{skipCaaPreflight:o.options.skipCaaPreflight}:{}});D=t.domainsDeployed>0,D&&(g=!0);for(const a of t.errors)l.push({accountId:"domains",error:C(a)})}if(r!==void 0&&!y&&I.length>0){const{id:t,name:a}=E.CASCADE_ACCOUNTS;e.onStepStart?.(t,a,d,p),e.onStepComplete?.(t,a,"skipped",d,p),e.onLog?.("Skipping account cascade \u2014 platform deployment failed; platform is a prerequisite for member accounts.","warn")}else if(I.length>0){const{id:t,name:a}=E.CASCADE_ACCOUNTS;e.onStepStart?.(t,a,d,p),e.onCascadePhaseStart?.("accounts");const c=B(o.orgConfig),P=G(I,c);(await V(P,K,async({account:n,region:k})=>{const m=F.get(`${n.id}-${$(k)}`),s=Date.now();return{result:await x(o,S,N,n,"account",e,{ipamPoolId:m,orgConfig:R,region:k,primaryRegion:_}),durationMs:Date.now()-s}})).forEach((n,k)=>{const m=P[k];if(!m)return;const s=m.account;if(n.status==="rejected"){const u=C(n.reason instanceof Error?n.reason.message:String(n.reason));A.push({accountId:s.id,accountName:s.name,region:m.region,result:"failed",durationMs:0,error:u}),l.push({accountId:s.id,error:u});return}const{result:f,durationMs:v}=n.value;if(f.success){const u=f.data.skipped===!0;u||(g=!0),f.data.outputs&&M.push({accountId:s.id,outputs:f.data.outputs}),A.push({accountId:s.id,accountName:s.name,region:m.region,result:u?"skipped":"succeeded",durationMs:v})}else{const u=C(f.error.message);A.push({accountId:s.id,accountName:s.name,region:m.region,result:"failed",durationMs:v,error:u}),l.push({accountId:s.id,error:u})}});const i=T(O(0));e.onCascadePhaseComplete?.("accounts"),e.onStepComplete?.(t,a,i.accountsFailed>0?"error":i.accountsSkipped===I.length?"skipped":"completed",d,p)}const L=O(Date.now()-U);return e.onCascadeComplete?.(T(L)),e.onCascadeLedger?.(L),{anyCascadeDeployHappened:g}}b(ce,"executeCascade");export{ce as executeCascade};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{failure as
|
|
1
|
+
var P=Object.defineProperty;var n=(t,e)=>P(t,"name",{value:e,configurable:!0});import{failure as a}from"@fjall/generator";import{collectStackOutputs as R}from"../contextHelpers.js";import{maskSensitiveOutput as u}from"@fjall/util";import{INFRA_STEP_NAME as o,STEP_IDS as r,STEP_NAMES as O}from"../../types/stepDefinitions.js";const i={CONNECT:{id:r.CONNECT,name:o.CONNECT},PREPARE:{id:r.PREPARE_ENVIRONMENT,name:o.PREPARE},DEPLOY:{id:r.DEPLOY,name:o.DEPLOY},MONITORING:{id:r.MONITORING,name:o.MONITORING},ORG_DEPLOY:{id:r.ORG_DEPLOY,name:O.ORG_DEPLOY},CASCADE_PLATFORM:{id:r.CASCADE_PLATFORM,name:O.CASCADE_PLATFORM},CASCADE_ACCOUNTS:{id:r.CASCADE_ACCOUNTS,name:O.CASCADE_ACCOUNTS}},N=4;function f(t,e){t.onStackTargets===void 0||e.length===0||t.onStackTargets(e)}n(f,"emitOrgStackTargets");function _(t){t.onStepComplete?.(i.PREPARE.id,i.PREPARE.name,"error",1,N)}n(_,"failPrepareStep");function d(t,e){const E=new Error(u(e));return t.onError?.(E),a(E)}n(d,"maskAndFail");async function D(t,e,E,S,C){const A=await t.cfnService.getStackOutputs(E,void 0,C);return A.success||e.onLog?.(S,"debug"),R(A)}n(D,"readStackOutputsBestEffort");export{i as INFRA_STEPS,N as INFRA_STEP_TOTAL,f as emitOrgStackTargets,_ as failPrepareStep,d as maskAndFail,D as readStackOutputsBestEffort};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{join as
|
|
1
|
+
var V=Object.defineProperty;var _=(o,e)=>V(o,"name",{value:e,configurable:!0});import{join as v}from"node:path";import{OrganizationsClient as X}from"@aws-sdk/client-organizations";import{success as Z,failure as B}from"@fjall/generator";import{ORGANISATION_TYPES as A,getOrganisationStackName as R}from"../../types/operations.js";import{synthOrFail as tt,bootstrapOrFail as et,forwardOutput as ot,forwardResourceProgress as at}from"../contextHelpers.js";import{partitionAccounts as rt,probeCascadeRoles as nt,buildRegionList as it,buildAccountRegionPairs as ct}from"../organisation/cascadeHelpers.js";import{assertLeaseBeforeStack as st}from"../application/leaseGate.js";import{accountTier as dt,maskSensitiveOutput as g}from"@fjall/util";import{findDevelopmentOuId as ut}from"../../aws/organisations/organisationalUnits.js";import{buildOrgContext as lt,resolveOrgDetails as gt}from"./orgContext.js";import{INFRA_STEPS as Y,emitOrgStackTargets as ft,maskAndFail as O,readStackOutputsBestEffort as z}from"./infraSteps.js";import{resolveCascadeAccounts as pt}from"./resolveCascadeAccounts.js";import{executeCascade as mt}from"./cascadeExecution.js";import{reconcileOrgTrailOutputs as St}from"./trailReconciliation.js";import{runDriftPreFlightGate as ht}from"../drift/preFlightGate.js";async function Ft(o,e,I,K){const{callbacks:t,options:D}=o,{providerAccounts:f,effectiveOrgConfig:U,defaultRegion:q}=await pt(o,e),p=await gt(e,o.abortSignal);if(!p.success)return O(t,p.error.message);const w=await ut(e.awsProvider.getClient(X),p.data.rootId,o.abortSignal);if(!w.success)return O(t,w.error.message);const J={...p.data,...w.data!==void 0?{devOuId:w.data}:{}},Q=f.find(a=>dt(a)==="organisation"),d=lt(o,e,I,"organisation",J,void 0,Q?.trailLifecycle),l=D?.cascade!==!1,{platformAccount:s,memberAccounts:P}=rt(f),E=l&&s!==void 0?1:0,L=l&&P.length>0?1:0,i=2+E+L,T=l?[...s!==void 0?[s]:[],...P]:[];if(T.length>0){const a=await nt(e,T,t,o.abortSignal),r=s!==void 0?a.find(n=>n.accountId===s.id):void 0;if(r!==void 0)return O(t,`Pre-flight cascade role check failed for the platform account ${r.accountName} (${r.accountId}): ${r.error}`);for(const n of a)t.onProgress?.({type:"warning",message:g(`Pre-flight cascade role check failed for ${n.accountName} (${n.accountId}) \u2014 its cascade deploy is expected to fail: ${n.error}`)})}t.onCascadeAccountsReconciled?.({hasPlatformAccount:E>0,hasMemberAccounts:L>0});const{id:C,name:b}=Y.PREPARE;t.onStepStart?.(C,b,0,i),t.onLog?.("Synthesising organisation infrastructure\u2026","info");const F=await tt(e,d,t,"CDK synthesis failed");if(!F.success)return t.onStepComplete?.(C,b,"error",0,i),F;const x=e.awsProvider.getAccountId(),N=e.awsProvider.getRegion();ft(t,[{stackName:R(A.ORGANISATION),...x!==""?{accountId:x}:{},...N!==""?{region:N}:{}},...l&&s!==void 0?[{stackName:R(A.PLATFORM),accountId:s.id,region:s.region??N}]:[],...l?ct(P,it(o.orgConfig)).map(({account:a,region:r})=>({stackName:R(A.ACCOUNT),accountId:a.id,region:r})):[]]);const G=await et(e,d,t);if(!G.success)return t.onStepComplete?.(C,b,"error",0,i),G;t.onStepComplete?.(C,b,"completed",0,i);const{id:m,name:S}=Y.ORG_DEPLOY,c=R(A.ORGANISATION);let H=!0;const h=await e.hashService.getTemplateHashes(v(d.path,"cdk.out"));if(h.success){const a=await e.hashService.compareWithState(h.data,d.path);a.success?H=a.data.stackChanges.get(c)??!0:t.onLog?.(g(`Org root change detection failed \u2014 deploying to be safe: ${a.error.message}`),"warn")}else t.onLog?.(g(`Org root template hashing failed \u2014 deploying to be safe: ${h.error.message}`),"warn");const $=H||D?.force===!0||!await e.cfnService.stackExists(c,void 0,o.abortSignal);t.onOrgChangesDetected?.({hasOrgChanges:$});let y;if($){t.onStepStart?.(m,S,1,i);const a=await ht(e.awsProvider,{stackNames:[c],cdkOutPath:v(d.path,"cdk.out"),cfnService:e.cfnService,callbacks:t,...o.abortSignal!==void 0?{abortSignal:o.abortSignal}:{}});if(a.kind==="blocked")return t.onStepComplete?.(m,S,"error",1,i),O(t,a.message);const r=await st(o.assertLeaseForStack,c,o.abortSignal);if(r!==void 0)return t.onStepComplete?.(m,S,"error",1,i),t.onError?.(r),B(r);const n=await e.cdkService.runCdkDeploy(d,c,ot(t),at(t),e.awsProvider);if(!n.success)return t.onStepComplete?.(m,S,"error",1,i),O(t,n.error);y=await z(e,t,c,"Failed to read org stack outputs (non-critical)",o.abortSignal);const j=h.success?h.data.get(c):void 0;if(j!==void 0){const W=await e.hashService.updateStateAfterDeploy(d.path,new Map([[c,j]]));W.success||t.onLog?.(`Warning: failed to update state file \u2014 next deploy may re-deploy the org root: ${g(W.error.message)}`,"warn")}t.onStepComplete?.(m,S,"completed",1,i)}else t.onLog?.("Organisation root: no infrastructure changes \u2014 skipping deploy","info"),y=await z(e,t,c,"Failed to read org stack outputs (non-critical)",o.abortSignal);const u=[],k=[];let M=$;if(l&&f.length>0){const{anyCascadeDeployHappened:a}=await mt(o,e,I,{providerAccounts:f,effectiveOrgConfig:U,totalSteps:i,cascadeErrors:u,allCascadeOutputs:k});if(a&&(M=!0),await St(o,e,{orgOutputs:y,allCascadeOutputs:k,orgDetails:p.data,providerAccounts:f,defaultRegion:q}),u.length>0){const r=u.map(n=>` ${n.accountId}: ${n.error}`).join(`
|
|
2
2
|
`);t.onLog?.(g(`Cascade failed for ${u.length} target(s):
|
|
3
|
-
${r}`),"warn")}}if(u.length>0){const
|
|
3
|
+
${r}`),"warn")}}if(u.length>0){const a=u.map(n=>g(`${n.accountId}: ${n.error}`)).join(`
|
|
4
4
|
`),r=new Error(`Organisation root deployed, but the cascade failed for ${u.length} target(s):
|
|
5
|
-
${
|
|
5
|
+
${a}`);return t.onError?.(r),B(r)}return Z({target:I.target,deploymentType:"organisation",outputs:y,artefacts:[],...k.length>0?{cascadeOutputs:k}:{},...M?{}:{noChanges:!0},durationMs:Date.now()-K})}_(Ft,"deployOrgWithCascade");export{Ft as deployOrgWithCascade};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{success as
|
|
1
|
+
var I=Object.defineProperty;var r=(o,n)=>I(o,"name",{value:n,configurable:!0});import{success as d,failure as s}from"@fjall/generator";import{OrganizationsClient as a}from"@aws-sdk/client-organizations";import{CdkContextBuilder as m}from"../../services/supporting/CdkContextBuilder.js";import{stubCallerIdentity as f}from"../../types/deployment/index.js";import{ensureOrganisationExists as C,describeOrganisation as O,describeOrganisationAsMember as v}from"../../aws/organisations/organisation.js";import{buildParamsContext as A,resolveAccountBootstrapSkipOidc as p}from"../contextHelpers.js";function h(o,n,e,t,i,u,g,l){const c=n.awsProvider.getRegion();return m.buildDeploymentContext({deployType:t,target:e.target,path:e.path,region:c,accountName:u,callerIdentity:f(n.awsProvider.getAccountId()),orgId:i?.orgId,rootId:i?.rootId,managementAccountId:i?.managementAccountId,devOuId:i?.devOuId,...A({orgConfig:o.orgConfig,identity:o.identity,skipOidc:p(t,o.orgConfig,o.options?.skipOidc,l),...t!=="organisation"?{region:c,primaryRegion:o.orgConfig?.primaryRegion}:{},trailLifecycle:g})},{verbose:o.options?.verbose,infraOnly:o.options?.infraOnly},o.orgConfig)}r(h,"buildOrgContext");async function B(o,n){const e=o.awsProvider.getClient(a),t=await C(e,n);return t.success?d({orgId:t.data.orgId,rootId:t.data.rootId,managementAccountId:t.data.managementAccountId}):s(t.error)}r(B,"resolveOrgDetails");async function D(o,n){const e=o.awsProvider.getClient(a),t=await v(e,n);return t.success?t.data===null?d(void 0):d({orgId:t.data.orgId,managementAccountId:t.data.managementAccountId,...t.data.rootId!==void 0?{rootId:t.data.rootId}:{}}):s(t.error)}r(D,"resolveOrgDetailsForMemberComponent");async function F(o,n){const e=o.awsProvider.getClient(a),t=await O(e,n,{tolerateAccessDenied:!0});if(!(!t.success||t.data===null))return{orgId:t.data.orgId,rootId:t.data.rootId,managementAccountId:t.data.managementAccountId}}r(F,"resolveOrgDetailsForSolo");export{h as buildOrgContext,B as resolveOrgDetails,D as resolveOrgDetailsForMemberComponent,F as resolveOrgDetailsForSolo};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cascadeHomeRegion as
|
|
1
|
+
var l=Object.defineProperty;var g=(o,i)=>l(o,"name",{value:i,configurable:!0});import{cascadeHomeRegion as d}from"../organisation/cascadeHelpers.js";import{reconcileProviderAccounts as f,mergeReconciledProviderAccounts as A}from"../organisation/reconcileProviderAccounts.js";import{accountTier as m,maskSensitiveOutput as a}from"@fjall/util";async function h(o,i){const{callbacks:c}=o;let n=o.orgConfig?.providerAccounts??[];if(n.length===0||n.every(e=>m(e)==="organisation")){const e=await f(i,o.workingDirectory);if(e.success){const{providerAccounts:t,missingAccountNames:r}=e.data;t.length>0&&(n=A(o.orgConfig,t).providerAccounts,c.onLog?.(`Reconciled ${t.length} account(s) from AWS Organizations`,"info")),r.length>0&&(c.onCascadeMissingAccounts?.(r),c.onProgress?.({type:"warning",message:a(`Accounts declared in ACCOUNTS but not yet in AWS Organizations (cascade will skip): ${r.join(", ")}`)}))}else c.onProgress?.({type:"warning",message:a(`Could not reconcile accounts from AWS Organizations \u2014 cascade may skip accounts: ${e.error.message}`)})}const s=d(o.orgConfig);n=n.map(e=>e.region!==void 0?e:{...e,region:s});const u=o.orgConfig!==void 0?{...o.orgConfig,providerAccounts:n}:n.length>0?{providerAccounts:n}:void 0;return{providerAccounts:n,effectiveOrgConfig:u,defaultRegion:s}}g(h,"resolveCascadeAccounts");export{h as resolveCascadeAccounts};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{join as
|
|
1
|
+
var y=Object.defineProperty;var R=(o,e)=>y(o,"name",{value:e,configurable:!0});import{join as T}from"path";import{failure as k,success as G}from"@fjall/generator";import{BackupClient as M}from"@aws-sdk/client-backup";import{IAMClient as Y}from"@aws-sdk/client-iam";import{logger as x}from"@fjall/util/logger";import{getOrganisationStackName as B}from"../../types/operations.js";import{accountHasDisasterRecovery as V,describeBackupVaultExists as _}from"../../aws/organisations/backup.js";import{describeAccountGlobalsExist as j,buildMissingAccountGlobalsAdvisory as H}from"../../aws/organisations/accountGlobals.js";import{targetsNonPrimaryRegion as $,reconcileOidcProviderIfCarried as K,synthOrFail as q,bootstrapOrFail as z,forwardOutput as J,forwardResourceProgress as Q}from"../contextHelpers.js";import{accountTier as N,getErrorMessage as U,maskSensitiveOutput as D}from"@fjall/util";import{runDriftPreFlightGate as W}from"../drift/preFlightGate.js";import{completeForgetAfterDeploy as X}from"../remediation/forgetResource.js";import{composePostFailureRepairOffer as Z}from"../remediation/postFailureRepairOffer.js";import{buildOrgContext as tt,resolveOrgDetailsForMemberComponent as ot,resolveOrgDetailsForSolo as et}from"./orgContext.js";import{hasOrganisationTierAccount as rt}from"../../aws/organisations/accountGlobals.js";import{INFRA_STEPS as n,INFRA_STEP_TOTAL as a,emitOrgStackTargets as nt,failPrepareStep as c,maskAndFail as l,readStackOutputsBestEffort as it}from"./infraSteps.js";import{assertLeaseBeforeStack as at}from"../application/leaseGate.js";async function Dt(o,e,d,g,I){const{callbacks:t}=o;t.onStepComplete?.(n.CONNECT.id,n.CONNECT.name,"completed",0,a),t.onStepStart?.(n.PREPARE.id,n.PREPARE.name,1,a);const b=e.awsProvider.getRegion(),f=o.orgConfig?.primaryRegion;if(f!==void 0&&$(b,f)){const r=await j(e.awsProvider.getClient(Y),o.abortSignal);if(!r.success)return c(t),l(t,r.error.message);if(!r.data)return c(t),l(t,H({target:d.target,deployRegion:b,primaryRegion:f,...o.orgConfig!==void 0?{providerAccounts:o.orgConfig.providerAccounts}:{}}))}let m;if(rt(o.orgConfig?.providerAccounts)){const r=await ot(e,o.abortSignal);if(!r.success)return c(t),l(t,r.error.message);m=r.data}else m=await et(e,o.abortSignal);const s=g==="account"?o.orgConfig?.providerAccounts.find(r=>r.id===e.awsProvider.getAccountId()):o.orgConfig?.providerAccounts.find(r=>N(r)==="platform"),h=g==="account"&&s!==void 0&&N(s)==="organisation",u=tt(o,e,d,g,m,g==="account"?s?.name??d.target:void 0,s?.trailLifecycle,h),S=o.orgConfig?.disasterRecoveryRegion;if(g==="account"&&(S!==void 0&&S!=="")&&(s===void 0||V(s.environment,S))){const r=await _(e.awsProvider.getClient(M),o.abortSignal);if(!r.success)return c(t),l(t,r.error.message);u.fjallAdoptBackupVault=r.data}t.onLog?.(`Synthesising ${g} infrastructure\u2026`,"info");const O=await q(e,u,t,"CDK synthesis failed");if(!O.success)return c(t),O;const i=B(d.type),C=e.awsProvider.getAccountId(),w=e.awsProvider.getRegion();nt(t,[{stackName:i,...C!==""?{accountId:C}:{},...w!==""?{region:w}:{}}]);const E=await W(e.awsProvider,{stackNames:[i],cdkOutPath:T(d.path,"cdk.out"),cfnService:e.cfnService,callbacks:t,...o.abortSignal!==void 0?{abortSignal:o.abortSignal}:{}});if(E.kind==="blocked")return c(t),l(t,E.message);const A=await z(e,u,t);if(!A.success)return c(t),A;t.onStepComplete?.(n.PREPARE.id,n.PREPARE.name,"completed",1,a);const p=await at(o.assertLeaseForStack,i,o.abortSignal);if(p!==void 0)return t.onError?.(p),k(p);t.onStepStart?.(n.DEPLOY.id,n.DEPLOY.name,2,a);const P=await e.cdkService.runCdkDeploy(u,i,J(t),Q(t),e.awsProvider);if(!P.success){t.onStepComplete?.(n.DEPLOY.id,n.DEPLOY.name,"error",2,a);const r=await Z(e.awsProvider,{stackNames:[i],repairTarget:`--stack ${i}`,originalError:new Error(D(P.error)),cfnService:e.cfnService,...o.abortSignal!==void 0?{abortSignal:o.abortSignal}:{}});return t.onError?.(r),k(r)}t.onStepComplete?.(n.DEPLOY.id,n.DEPLOY.name,"completed",2,a);const F=await it(e,t,i,"Failed to read stack outputs (non-critical)",o.abortSignal);await K(u,e.awsProvider,t,o.abortSignal),t.onStepStart?.(n.MONITORING.id,n.MONITORING.name,3,a),t.onStepComplete?.(n.MONITORING.id,n.MONITORING.name,"completed",3,a),await X(e.awsProvider,{stackNames:[i],onLog:R((r,v)=>t.onLog?.(r,v),"onLog"),...o.abortSignal!==void 0?{abortSignal:o.abortSignal}:{}}).catch(r=>{x.debug("SingleComponentDeploy","Forget completion sweep failed",{error:D(U(r))})});const L=P.data?.status==="NO_CHANGES"&&o.options?.force!==!0;return G({target:d.target,deploymentType:"organisation",outputs:F,artefacts:[],...L?{noChanges:!0}:{},durationMs:Date.now()-I})}R(Dt,"deploySingleComponent");export{Dt as deploySingleComponent};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{success as
|
|
1
|
+
var w=Object.defineProperty;var c=(i,e)=>w(i,"name",{value:e,configurable:!0});import{success as m,failure as I}from"@fjall/generator";import{S3Client as s}from"@aws-sdk/client-s3";import{CloudTrailClient as f}from"@aws-sdk/client-cloudtrail";import{KMSClient as C}from"@aws-sdk/client-kms";import{assumeCascadeRole as S}from"../contextHelpers.js";import{reconcileTrailMigration as P,ORG_TRAIL_BUCKET_OUTPUT_KEY as R,TRAIL_BUCKET_OUTPUT_KEY as _}from"../trailMigration/trailMigration.js";import{getErrorMessage as b,maskSensitiveOutput as v}from"@fjall/util";async function x(i,e,u){const{callbacks:d}=i,{orgOutputs:o,allCascadeOutputs:p,orgDetails:a,providerAccounts:T}=u,l=o?.[R];if(l!==void 0&&l!==""){const r=new Map;for(const t of p)(r.get(t.accountId)===void 0||_ in t.outputs)&&r.set(t.accountId,t.outputs);o!==void 0&&r.set(a.managementAccountId,o);const O=c(async(t,g)=>{if(t.id===a.managementAccountId)return m({cloudTrailClient:e.awsProvider.getClient(f),s3Client:e.awsProvider.getClient(s),kmsClient:e.awsProvider.getClient(C)});const n=await S(e.awsProvider,t.id,g,`fjall-trail-migration-${t.name}`,i.abortSignal);return n.success?m({cloudTrailClient:n.data.provider.getClient(f),s3Client:n.data.provider.getClient(s),kmsClient:n.data.provider.getClient(C)}):I(n.error)},"getMemberClients");try{await P({managementS3Client:e.awsProvider.getClient(s),getMemberClients:O},{orgId:a.orgId,orgTrailBucketName:l,accounts:T,cascadeOutputs:r,defaultRegion:u.defaultRegion,...i.abortSignal!==void 0?{abortSignal:i.abortSignal}:{}},d)}catch(t){d.onLog?.(v(`Org-trail migration reconciliation failed (non-fatal): ${b(t)}`),"warn")}}}c(x,"reconcileOrgTrailOutputs");export{x as reconcileOrgTrailOutputs};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{DescribeTableCommand as
|
|
1
|
+
var m=Object.defineProperty;var o=(e,n)=>m(e,"name",{value:n,configurable:!0});import{DescribeTableCommand as D,DynamoDBClient as u}from"@aws-sdk/client-dynamodb";import{DescribeDBClustersCommand as f,DescribeDBInstancesCommand as p,RDSClient as b}from"@aws-sdk/client-rds";import{success as r,failure as s}from"@fjall/generator";import{getErrorMessage as a,maskSensitiveOutput as c}from"@fjall/util";import{composeSdkAbortSignal as l}from"../../aws/organisations/types.js";function d(e){const n=typeof e=="object"&&e!==null&&"name"in e?String(e.name):"";return/NotFound/i.test(n)}o(d,"isNotFoundError");async function C(e,n,i){try{const t=await e.getClient(b).send(new p({DBInstanceIdentifier:n}),{abortSignal:l(i)});return r({blocked:t.DBInstances?.[0]?.DeletionProtection===!0,setting:"DeletionProtection"})}catch(t){return d(t)?r({blocked:!1,setting:"DeletionProtection"}):s(new Error(c(a(t))))}}o(C,"probeDbInstanceDeletionProtection");async function g(e,n,i){try{const t=await e.getClient(b).send(new f({DBClusterIdentifier:n}),{abortSignal:l(i)});return r({blocked:t.DBClusters?.[0]?.DeletionProtection===!0,setting:"DeletionProtection"})}catch(t){return d(t)?r({blocked:!1,setting:"DeletionProtection"}):s(new Error(c(a(t))))}}o(g,"probeDbClusterDeletionProtection");async function y(e,n,i){try{const t=await e.getClient(u).send(new D({TableName:n}),{abortSignal:l(i)});return r({blocked:t.Table?.DeletionProtectionEnabled===!0,setting:"DeletionProtectionEnabled"})}catch(t){return d(t)?r({blocked:!1,setting:"DeletionProtectionEnabled"}):s(new Error(c(a(t))))}}o(y,"probeDynamoTableDeletionProtection");const I={"AWS::RDS::DBInstance":{probe:C,method:"rds:DescribeDBInstances",disableCommand:o(e=>`aws rds modify-db-instance --db-instance-identifier ${e} --no-deletion-protection`,"disableCommand")},"AWS::RDS::DBCluster":{probe:g,method:"rds:DescribeDBClusters",disableCommand:o(e=>`aws rds modify-db-cluster --db-cluster-identifier ${e} --no-deletion-protection`,"disableCommand")},"AWS::DynamoDB::Table":{probe:y,method:"dynamodb:DescribeTable",disableCommand:o(e=>`aws dynamodb update-table --table-name ${e} --no-deletion-protection-enabled`,"disableCommand")}};export{I as DELETE_BLOCK_PROBES};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{remediationPlaceholderPhysicalId as
|
|
1
|
+
var f=Object.defineProperty;var n=(i,r)=>f(i,"name",{value:r,configurable:!0});import{remediationPlaceholderPhysicalId as p}from"../../types/remediation.js";import{computeTicketDigest as m,evaluateDestructionConsents as b,executableRemediationVerbs as d,legalRemediationVerbs as h}from"../application/destructionGate.js";const v="drift-repair";function k(i){const r=i.filter(e=>e.verdict==="confirmed-deleted").map(e=>({stack:e.stackName,logicalId:e.logicalId,resourceType:e.resourceType,physicalName:e.physicalId??p(e.stackName,e.logicalId),finding:"resource-deleted",cause:`deleted outside CloudFormation \u2014 ${e.verdictDetail}`,offendingProperties:[],verdictSource:"drift-probe",dataLossRisk:!1,verbs:[...h("resource-deleted")]}));if(r.length!==0)return{ticketDigest:m(v,r),resources:r}}n(k,"buildDriftRepairTicket");function x(i){if(i.verdict!=="confirmed-deleted")return[...i.verbs];const r=d("resource-deleted");return i.verbs.filter(e=>r.includes(e))}n(x,"advertisableDriftVerbs");function R(i){const{ticket:r,consents:e,consentDigest:l}=i,c=b({ticket:r,consents:e,...l!==void 0?{consentDigest:l}:{}}),u=new Map(r.resources.map(t=>[t.physicalName,t])),s=[],a=[];if(c.satisfied)for(const t of c.rows){if(t.verdict!=="matched")continue;const o=u.get(t.physicalName);if(o!==void 0){if(t.verb==="abort"){s.push(`${t.physicalName}: aborted by operator consent`);continue}d(o.finding).includes(t.verb)||a.push(`${t.physicalName}: verb '${t.verb}' is accepted vocabulary but not yet executable via 'fjall drift repair' for ${o.finding} \u2014 executable today: ${d(o.finding).join(", ")||"none (abort only)"}`)}}return c.satisfied&&s.length===0&&a.length===0?{kind:"proceed",verbsByPhysicalName:new Map(c.rows.filter(o=>o.verdict==="matched").map(o=>[o.physicalName,o.verb])),report:c}:{kind:"withheld",outcome:s.length>0?"declined":a.length>0?"consent-not-executable":c.rows.length>0||c.digestMismatch?"consents-invalid":"awaiting-consent",report:c,reasons:[...s,...a]}}n(R,"runDriftRepairGate");export{x as advertisableDriftVerbs,k as buildDriftRepairTicket,R as runDriftRepairGate};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{CloudFormationClient as
|
|
1
|
+
var O=Object.defineProperty;var g=(m,o)=>O(m,"name",{value:o,configurable:!0});import{CloudFormationClient as D,DescribeStackEventsCommand as B,DescribeStacksCommand as K,GetTemplateCommand as _}from"@aws-sdk/client-cloudformation";import{mkdir as x,rename as I,writeFile as J}from"node:fs/promises";import{homedir as M}from"node:os";import{join as S}from"node:path";import{success as R,failure as l}from"@fjall/generator";import{getErrorMessage as P,maskSensitiveOutput as k}from"@fjall/util";import{composeSdkAbortSignal as b,isAborted as G}from"../../aws/organisations/types.js";const w=50,U=20;function V(){return S(M(),".fjall","forensics","remediation")}g(V,"defaultRemediationForensicsDir");async function Y(m,o,v){const{stackName:t,opId:h,abortSignal:i}=o,d=m.getClient(D);let f,y,E,T,N,u,j;try{const e=await d.send(new _({StackName:t,TemplateStage:"Original"}),{abortSignal:b(i)});if(e.TemplateBody===void 0||e.TemplateBody==="")return l(new Error(`Forensics capture aborted the remediation of ${t}: GetTemplate returned an empty template body \u2014 nothing was mutated.`));f=e.TemplateBody;const n=(await d.send(new K({StackName:t}),{abortSignal:b(i)})).Stacks?.[0];if(n===void 0)return l(new Error(`Forensics capture aborted the remediation of ${t}: DescribeStacks returned no stack \u2014 nothing was mutated.`));y=n,E=n.Capabilities??[],T=(n.Parameters??[]).flatMap(r=>r.ParameterKey!==void 0?[r.ParameterKey]:[]),N=n.StackStatus??"UNKNOWN";const c=[];let a,A=0;do{if(G(i))throw new Error(`aborted while reading the stack events of ${t}`);const r=await d.send(new B({StackName:t,...a!==void 0?{NextToken:a}:{}}),{abortSignal:b(i)});c.push(...r.StackEvents??[]),a=r.NextToken,A+=1}while(a!==void 0&&c.length<w&&A<U);j=a!==void 0||c.length>w,u=c.slice(0,w)}catch(e){return l(new Error(`Forensics capture failed for ${t} \u2014 aborting before any mutation: ${k(P(e))}`))}const F=v?.forensicsDir??V(),C=(v?.now?.()??new Date).toISOString(),s=S(F,h,C.replace(/[^0-9A-Za-z.-]/g,"")),$=[{name:"live-template.json",content:f},{name:"stack-description.json",content:JSON.stringify(y,null,2)},{name:"stack-events.json",content:JSON.stringify(u,null,2)},{name:"drift-findings.json",content:JSON.stringify(o.findings,null,2)},{name:"manifest.json",content:JSON.stringify({opId:h,stackName:t,capturedAt:C,files:["live-template.json","stack-description.json","stack-events.json","drift-findings.json"],...j?{eventsTruncated:!0,eventsCaptured:u.length}:{}},null,2)}];try{await x(s,{recursive:!0});for(const e of $){const p=S(s,e.name),n=`${p}.tmp-${process.pid}`;await J(n,k(e.content),"utf8"),await I(n,p)}}catch(e){return l(new Error(`Forensics capture could not persist the evidence bundle for ${t} at ${s} \u2014 aborting before any mutation: ${k(P(e))}`))}return R({forensicsPath:s,files:$.map(e=>e.name),liveTemplateBody:f,capabilities:E,parameterKeys:T,stackStatus:N})}g(Y,"captureRemediationForensics");export{Y as captureRemediationForensics,V as defaultRemediationForensicsDir};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readFile as ge}from"node:fs/promises";import{join as he}from"node:path";import{CloudFormationClient as me,DescribeStacksCommand as ye,GetTemplateCommand as ve}from"@aws-sdk/client-cloudformation";import{success as $,failure as j}from"@fjall/generator";import{getErrorMessage as q,maskSensitiveOutput as _}from"@fjall/util";import{logger as J}from"@fjall/util/logger";import{composeSdkAbortSignal as Z,isAborted as ee}from"../../aws/organisations/types.js";import{isRemediationPlaceholderPhysicalId as H,remediationPhaseRank as A}from"../../types/remediation.js";import{DriftProbe as te,MEMBERSHIP_GATE_PROBE_METHOD as re,probePhysicalPresence as ae}from"../drift/driftProbe.js";import{clearDriftSuspects as be,readDriftSuspects as we,recordDriftSuspects as Ie}from"../drift/driftSuspectJournal.js";import{captureRemediationForensics as $e}from"./forensicsCapture.js";import{findDeadReferencesFromTargets as ke,findTargetReferrers as Se}from"./removalUpdate.js";import{archiveCompletedRemediationJournal as Re,computeRemediationOpId as ne,formatArchivedOperationNotice as Ne,formatCompletedOperationNotice as Te,listActiveRemediationOps as z,listActiveRemediationOpsForContext as Pe,readRemediationJournal as oe,sweepExpiredRemediationJournals as Ce,writeRemediationJournal as ie}from"./remediationJournal.js";import{applyRetainFlip as je,computeTemplateDigest as se,deriveRetainFlipTemplate as Oe,findMarkedResourcesByOpId as V,proveFlipMetadataOnly as Ee,readResourceTypes as Fe,readRetainFlipState as M,verifyRetainFlipDiff as Ae,FORGET_MARKER_KEY as ce,RECREATE_MARKER_KEY as Be}from"./retainFlip.js";const U="ForgetResource";async function x(n,t,s){const r=n.getClient(me);try{const d=await r.send(new ve({StackName:t,TemplateStage:"Original"}),{abortSignal:Z(s)});if(d.TemplateBody===void 0||d.TemplateBody==="")return j(new Error(`GetTemplate returned an empty template body for ${t}`));const o=(await r.send(new ye({StackName:t}),{abortSignal:Z(s)})).Stacks?.[0];return $({templateBody:d.TemplateBody,capabilities:o?.Capabilities??[],parameterKeys:(o?.Parameters??[]).flatMap(c=>c.ParameterKey!==void 0?[c.ParameterKey]:[])})}catch(d){return j(new Error(`Could not read the live state of ${t}: ${_(q(d))}`))}}function de(n){const t=`'fjall deploy${n!==void 0?` ${n}`:""}'`;return`Two cures terminate it: re-apply the replacement-requiring configuration change and re-run ${t}, re-consenting when prompted \u2014 the recreate completes; or remove the resource from the fjall config and run ${t} \u2014 the operation closes as removed.`}async function Ze(n,t,s){const r=t.targets.map(f=>f.logicalId),d=ne(t.stackName,r),a=await oe(n.getAccountId(),n.getRegion(),t.stackName,d,s?.journal);if(a!==null&&a.verb!=="forget"&&a.phase!=="done")return j(new Error(`Forget operation ${d} on ${t.stackName} collides with an in-flight '${a.verb}' operation covering the same resources \u2014 that surgery resumes only through its deploy. ${de(a.appName)} Nothing was mutated.`));if(a!==null&&a.phase!=="done")return $({opId:d,alreadyConsented:!0,source:"journal",journal:a,foreignMarkers:[]});const o=await x(n,t.stackName,t.abortSignal);if(!o.success)return o;const c=M(o.data.templateBody,r,t.stackName);if(!c.success)return c;const e=c.data.markerOpIds.filter(f=>f!==d).map(f=>{const l=V(o.data.templateBody,f,t.stackName),u=l.success?l.data:[];return{opId:f,markedLogicalIds:u,markedResourceTypes:Fe(o.data.templateBody,u,t.stackName)}});return c.data.markerOpIds.includes(d)?$({opId:d,alreadyConsented:!0,source:"marker",foreignMarkers:e}):$({opId:d,alreadyConsented:!1,source:"none",foreignMarkers:e})}function le(n,t,s,r,d){const a=r.filter(e=>e!==s);if(a.length===0)return $(void 0);const o=a.map(e=>{const f=V(n,e,t);return`${e} covers ${f.success&&f.data.length>0?f.data.join(", "):"(could not recover its target set)"}`}),c=d?`resume this operation with 'fjall drift resume ${s}'`:"re-run 'fjall drift repair' to restart this operation";return j(new Error(`Forget operation ${s} on ${t} found target resources already carrying a ${ce} marker from a different operation \u2014 its journal may live on another machine, and completing this operation over it would strand that surgery. Marked resources recovered from the live template: ${o.join("; ")}. Complete the foreign operation first \u2014 on the machine that holds its journal, run 'fjall drift resume ${a.join("' / 'fjall drift resume ")}' (or 'fjall drift repair' there if it is journal-less); once it converges, ${c}. Nothing was mutated.`))}async function Q(n,t,s,r,d){const a=s.targets.map(i=>i.logicalId),o=M(t,a,s.stackName);if(!o.success)return o;if(A(s.phase)<A("flip-applied"))return $({absent:o.data.absentTargets,retained:[]});const c=V(t,s.opId,s.stackName,s.verb==="recreate"?Be:ce);if(!c.success)return c;const e=new Set(c.data),f=new Set(o.data.absentTargets),l=s.targets.filter(i=>!f.has(i.logicalId)&&!e.has(i.logicalId));if(l.length===0)return $({absent:o.data.absentTargets,retained:[]});const u=await pe(n,s,l,r,d);return $({absent:o.data.absentTargets,retained:l.map(i=>i.logicalId).filter(i=>u.has(i))})}async function pe(n,t,s,r,d){const a=s.map(e=>({stackName:t.stackName,logicalId:e.logicalId,resourceType:e.resourceType,...H(e,t.stackName)?{}:{physicalId:e.physicalId},failedStatus:t.verb==="recreate"?"RECREATE_CLOSURE_CHECK":"FORGET_CLOSURE_CHECK",statusReason:`Presence verification at ${t.verb} closure`})),c=await new te(n,d?.probe).probeSuspects({stackName:t.stackName,suspects:a,...r!==void 0?{abortSignal:r}:{}});return c.success?new Set(c.data.filter(e=>e.verdict==="present"&&e.probeMethod!==re).map(e=>e.logicalId)):(J.debug(U,"Closure probe failed \u2014 failing closed (not retained)",{stackName:t.stackName,opId:t.opId,error:c.error.message}),new Set)}async function et(n,t,s){const{stackName:r,targets:d,onProgress:a,abortSignal:o}=t;if(d.length===0)return j(new Error("Forget surgery requires at least one target"));const c=s?.now??(()=>new Date),e=n.getAccountId(),f=n.getRegion(),l=d.map(h=>h.logicalId),u=ne(r,l);let i=await oe(e,f,r,u,s?.journal);if(i!==null&&i.verb!=="forget"&&i.phase!=="done")return j(new Error(`Forget surgery ${u} on ${r} collides with an in-flight '${i.verb}' operation covering the same resources \u2014 that surgery resumes only through its deploy. ${de(i.appName)} Nothing was mutated.`));if(i!==null&&i.phase==="done"){if(t.doneJournalPolicy!=="archive-and-restart")return a?.(Te(i)),$({opId:u,phase:"done",convergePending:!1,...i.forensicsPath!==void 0?{forensicsPath:i.forensicsPath}:{},journal:i});const h=await Re(i,s?.journal);if(!h.success)return h;a?.(Ne(i)),i=null}const k=async h=>{const N=await ie(h,s?.journal);return N.success?$(h):j(N.error)};let b;if(i===null){const h=await x(n,r,o);if(!h.success)return h;const N=M(h.data.templateBody,l,r);if(!N.success)return N;const D=le(h.data.templateBody,r,u,N.data.markerOpIds,!1);if(!D.success)return D;if(N.data.markerOpIds.includes(u)){a?.(`Recognised in-flight forget markers on ${r} \u2014 resuming from the fjall:forget marker`);const m=c().toISOString(),T=await k({version:1,opId:u,verb:"forget",accountId:e,region:f,stackName:r,...t.appName!==void 0?{appName:t.appName}:{},targets:[...d],phase:N.data.allFlipped?"flip-applied":"captured",createdAt:m,updatedAt:m});if(!T.success)return T;i=T.data,b=h.data}else{a?.(`Capturing forensics for ${r} (${u})`);const m=await $e(n,{stackName:r,opId:u,findings:t.findings??[],...o!==void 0?{abortSignal:o}:{}},s?.forensics);if(!m.success)return m;const T=c().toISOString(),I=await k({version:1,opId:u,verb:"forget",accountId:e,region:f,stackName:r,...t.appName!==void 0?{appName:t.appName}:{},targets:[...d],phase:"captured",createdAt:T,updatedAt:T,forensicsPath:m.data.forensicsPath,...t.consentedPhysicalNames!==void 0?{consentedPhysicalNames:[...t.consentedPhysicalNames]}:{}});if(!I.success)return I;i=I.data,b={templateBody:m.data.liveTemplateBody,capabilities:m.data.capabilities,parameterKeys:m.data.parameterKeys}}}if(b===void 0){const h=await x(n,r,o);if(!h.success)return h;b=h.data}let g=b.templateBody,p=M(g,l,r);if(!p.success)return p;const R=le(g,r,u,p.data.markerOpIds,!0);if(!R.success)return R;const O=i.phase,B=await Q(n,g,i,o,s);if(!B.success)return B;const w=new Set(B.data.retained),E=p.data.policyPendingTargets.filter(h=>!w.has(h)),F=[];let L=!1;if(E.length>0){const h=Se(g,E,r);if(!h.success)return h;if(h.data.length>0){const y=E.length>1;return j(new Error(`Forget surgery cannot proceed on ${r}: ${E.join(", ")} must have DeletionPolicy changed to Retain before removal, but other resources in the stack still reference the deleted resource${y?"s":""} (${h.data.join(", ")}) \u2014 CloudFormation re-resolves references to a deleted resource during any update that modifies it, so the policy change cannot be applied. Either restore the deleted resource${y?"s":""} out-of-band and re-run 'fjall drift detect' (the finding clears; this operation record then idles until the 90-day retention sweep), or remove the target and every referencing resource from the stack in one manual template update \u2014 they cannot outlive it. This shape is a recorded fjall limitation (drift-remediation ADR, 2026-07-12). Nothing was mutated.`))}const N=l.filter(y=>!w.has(y)),D=ke(g,E,N,r);if(!D.success)return D;let m=D.data;if(m.length>0){const y=new Set(m.flatMap(v=>v.deadReferences)),S=await pe(n,i,i.targets.filter(v=>y.has(v.logicalId)),o,s);S.size>0&&(m=m.map(v=>({...v,deadReferences:v.deadReferences.filter(P=>!S.has(P))})).filter(v=>v.deadReferences.length>0))}if(m.length>0){const y=[];for(const S of m){const v=i.targets.find(C=>C.logicalId===S.pendingTarget);if(v===void 0||H(v,r)){y.push(`${S.pendingTarget} (no probeable physical id on this operation)`);continue}const P=await ae(n,{resourceType:v.resourceType,physicalId:v.physicalId,...o!==void 0?{abortSignal:o}:{}});P.verdict==="confirmed-deleted"?F.push(S.pendingTarget):y.push(`${S.pendingTarget} (${P.verdict}: ${P.detail})`)}if(y.length>0){const S=m.map(C=>`${C.pendingTarget} itself references the deleted ${C.deadReferences.join(", ")}`).join("; "),v=new Set(m.flatMap(C=>C.deadReferences)).size>1,P=m.length>1;return j(new Error(`Forget surgery cannot proceed on ${r}: ${m.map(C=>C.pendingTarget).join(", ")} must have DeletionPolicy changed to Retain before removal, but ${S} \u2014 CloudFormation resolves a modified resource's own references at execution time, and a reference to a deleted resource cannot resolve, so the policy change cannot be applied. fjall proceeds without the policy change only when the referencing target is verified deleted against its owning service (the converge's removal is then a physical no-op), and verification did not confirm deletion for ${y.join("; ")}. Either restore the referenced resource${v?"s":""} out-of-band and resume this operation (the policy change can then apply), or remove the referencing target${P?"s":""} and every reference to ${P?"them":"it"} (including stack Outputs) from the stack in one manual template update \u2014 CloudFormation deletes template-removed resources from its stored state, so removing an already-deleted resource succeeds where modifying it cannot. The unverified-absence shape is a recorded fjall limitation (drift-remediation ADR, 2026-07-12). Nothing was mutated.`))}a?.(`${F.join(", ")} cannot carry the Retain flip (${m.map(S=>`${S.pendingTarget} references the deleted ${S.deadReferences.join(", ")}`).join("; ")}) but ${F.length===1?"is":"are"} verified deleted against the owning service \u2014 proceeding without the flip: the converge's removal of an already-deleted resource is a physical no-op (flipless by necessity)`)}const T=new Set(F),I=E.filter(y=>!T.has(y));if(I.length>0){L=!0,i.liveTemplateDigest!==void 0&&i.liveTemplateDigest!==se(g)&&A(i.phase)>=A("flip-verified")&&a?.(`Live template of ${r} changed since the flip was verified \u2014 re-verifying from the current template`),a?.(`Deriving the retain flip for ${r}`);const y=Oe({liveTemplateBody:g,stackName:r,targets:I,opId:u,at:c().toISOString()});if(!y.success)return y;const S=Ae(g,y.data.flipTemplateBody,I,r);if(!S.success)return S;a?.("Proving the flip is metadata-only (review change set)");const v=await Ee(n,{stackName:r,flipTemplateBody:y.data.flipTemplateBody,targets:I,parameterKeys:b.parameterKeys,...o!==void 0?{abortSignal:o}:{}},s?.flip);if(!v.success)return v;const P=await k({...i,phase:"flip-verified",updatedAt:c().toISOString(),liveTemplateDigest:y.data.liveTemplateDigest,proof:{diffGuardPassed:!0,changeSetMetadataOnly:!0,noChanges:v.data.noChanges,provenAt:c().toISOString()}});if(!P.success)return P;i=P.data,a?.(`Applying the retain flip to ${r}`);const C=await je(n,{stackName:r,flipTemplateBody:y.data.flipTemplateBody,capabilities:b.capabilities,parameterKeys:b.parameterKeys,derivedFromTemplateDigest:y.data.liveTemplateDigest,opId:u,...o!==void 0?{abortSignal:o}:{}},s?.flip);if(!C.success)return C;const Y=await x(n,r,o);if(!Y.success)return Y;if(g=Y.data.templateBody,p=M(g,l,r),!p.success)return p;const ue=new Set(I),W=p.data.unflippedTargets.filter(X=>ue.has(X)&&!w.has(X));if(W.length>0)return j(new Error(`Retain flip on ${r} reported ${C.data.outcome} but GetTemplate does not show Retain + the fjall:forget marker on ${W.join(", ")} \u2014 aborting to manual repair.`))}}if(A(i.phase)<A("flip-applied")){const h=new Set(F),N=p.data.policyPendingTargets.filter(I=>!w.has(I)&&!h.has(I));if(N.length>0)return j(new Error(`Retain flip on ${r} could not be recorded: ${N.join(", ")} do not carry the Retain deletion policies in the live template \u2014 re-run 'fjall drift repair' to re-verify.`));const D=new Set(p.data.absentTargets),m=p.data.unflippedTargets.filter(I=>!D.has(I)&&!w.has(I)&&!h.has(I));m.length>0&&a?.(`${m.join(", ")} already ${m.length===1?"carries":"carry"} DeletionPolicy Retain in the live template \u2014 no stack update needed (flipless fast-path)`);const T=await k({...i,phase:"flip-applied",updatedAt:c().toISOString(),liveTemplateDigest:se(g),...m.length>0?{fliplessTargets:m}:{},...F.length>0?{fliplessByNecessity:F}:{}});if(!T.success)return T;i=T.data}const K=!L&&i.phase===O?B:await Q(n,g,i,o,s);if(!K.success)return K;const G=K.data.retained;if(K.data.absent.length+G.length===l.length){const h=await fe(i,G,c,s?.journal,s?.driftJournal);return h.success?(i=h.data,a?.(G.length>0?`Forget operation ${u} complete \u2014 the converge kept ${G.join(", ")} (re-declared in the synthesised template)${K.data.absent.length>0?` and ${r} no longer tracks the remaining targets`:""}`:`Targets are no longer in the ${r} template \u2014 forget operation ${u} complete`),$({opId:u,phase:"done",convergePending:!1,...i.forensicsPath!==void 0?{forensicsPath:i.forensicsPath}:{},journal:i})):h}return a?.(`Retain flip is live on ${r} \u2014 run the converge deploy to complete the forget`),$({opId:u,phase:i.phase,convergePending:!0,...i.forensicsPath!==void 0?{forensicsPath:i.forensicsPath}:{},journal:i})}async function De(n,t,s){const{journal:r}=t;if(r.phase==="done")return $({status:"done",journal:r});const d=s?.now??(()=>new Date),a=r.targets.map(l=>l.logicalId),o=await x(n,r.stackName,t.abortSignal);if(!o.success)return o;const c=await Q(n,o.data.templateBody,r,t.abortSignal,s);if(!c.success)return c;if(c.data.absent.length+c.data.retained.length!==a.length){const l=new Set([...c.data.absent,...c.data.retained]);return $({status:"converge-pending",journal:r,absent:[...c.data.absent],retained:[...c.data.retained],unverified:a.filter(u=>!l.has(u))})}const e=r.verb==="recreate"&&c.data.absent.length>0?await Me(n,r,c.data.absent,t.abortSignal):void 0,f=await fe(r,c.data.retained,d,s?.journal,s?.driftJournal,e);return f.success?$({status:"done",journal:f.data}):f}async function Me(n,t,s,r){const d={orphaned:[],unverified:[]};for(const a of s){const o=t.targets.find(e=>e.logicalId===a);if(o===void 0)continue;if(H(o,t.stackName)){d.unverified.push(a);continue}const c=await ae(n,{resourceType:o.resourceType,physicalId:o.physicalId,...r!==void 0?{abortSignal:r}:{}});c.verdict==="present"?d.orphaned.push(a):c.verdict==="unconfirmed"&&d.unverified.push(a)}return d}async function fe(n,t,s,r,d,a){const o=s().toISOString(),c={...n,phase:"done",updatedAt:o,completedAt:o,...t.length>0?n.verb==="recreate"?{recreatedResources:[...t]}:{retainedByRedeclaration:[...t]}:{},...a!==void 0&&a.orphaned.length>0?{orphanedResources:[...a.orphaned]}:{},...a!==void 0&&a.unverified.length>0?{unverifiedRemovals:[...a.unverified]}:{}},e=await ie(c,r);return e.success?(await xe(n,d).catch(f=>{J.warn(U,"Failed to clear drift suspects after forget",{stackName:n.stackName,opId:n.opId,error:_(q(f))})}),$(c)):j(e.error)}async function xe(n,t){const s=await we(n.accountId,n.region,n.stackName,t);if(s===null)return;const r=new Set(n.targets.map(a=>a.logicalId)),d=s.suspects.filter(a=>!r.has(a.logicalId));if(d.length!==s.suspects.length){if(d.length===0){await be(n.accountId,n.region,n.stackName,t);return}await Ie({...s,suspects:d},t)}}async function tt(n,t,s){try{const r=await ge(he(n,`${t}.template.json`),"utf8"),d=JSON.parse(r);return typeof d.Resources=="object"&&d.Resources!==null&&Object.hasOwn(d.Resources,s)}catch(r){J.debug(U,"Could not read the synthesised template",{stackName:t,error:_(q(r))});return}}async function rt(n,t,s){await Ce(s?.journal,s?.now);const r=n.aws.getAccountId(),d=n.aws.getRegion(),a={blocked:[],inProgressOpIds:[],converging:[],flipNotLive:[],flipUnverified:[],stillDeclared:[],recreateConverging:[]},o=new Map,c=new Map;for(const e of t){if(ee(n.abortSignal)){a.blocked.push(e);continue}let f=o.get(e.stackName);f===void 0&&(f=await z(r,d,e.stackName,s?.journal),o.set(e.stackName,f));const l=f.find(p=>p.verb==="forget"&&p.targets.some(R=>R.logicalId===e.logicalId));if(l===void 0){const p=f.find(R=>R.verb==="recreate"&&R.targets.some(O=>O.logicalId===e.logicalId));if(p!==void 0){a.recreateConverging.push({finding:e,opId:p.opId,...p.appName!==void 0?{appName:p.appName}:{}});continue}a.blocked.push(e);continue}if(A(l.phase)<A("flip-applied")){a.blocked.push(e),a.inProgressOpIds.includes(l.opId)||a.inProgressOpIds.push(l.opId);continue}let u=c.get(e.stackName);if(u===void 0){const p=await x(n.aws,e.stackName,n.abortSignal);p.success?u={templateBody:p.data.templateBody}:(J.debug(U,"Could not read the live template for the [G2] partition",{stackName:e.stackName,error:p.error.message}),u={errorMessage:p.error.message}),c.set(e.stackName,u)}if("errorMessage"in u){a.blocked.push(e),a.flipUnverified.push({finding:e,opId:l.opId,message:u.errorMessage});continue}const i=M(u.templateBody,[e.logicalId],e.stackName),k=new Set(l.fliplessByNecessity??[]);if(!(i.success&&(i.data.absentTargets.length===1||(i.data.policyPendingTargets.length===0||k.has(e.logicalId))&&i.data.markerOpIds.every(p=>p===l.opId)))){a.blocked.push(e),a.flipNotLive.push({finding:e,opId:l.opId});continue}if(await n.synthDeclaresTarget(e.stackName,e.logicalId)===!0){a.blocked.push(e),a.stillDeclared.push({finding:e,opId:l.opId});continue}a.converging.push({finding:e,opId:l.opId})}return a}async function at(n,t,s,r){const d=n.aws.getAccountId(),a=n.aws.getRegion(),o={verifiedAbsent:[],blocked:[]};for(const c of t){const e=await z(d,a,c,r?.journal);for(const f of e){if(f.verb!=="forget"||A(f.phase)<A("flip-applied"))continue;const l=[];for(const g of f.fliplessByNecessity??[]){const p={stackName:c,logicalId:g,opId:f.opId};if(s.get(c)?.has(g)===!0){o.verifiedAbsent.push(p);continue}if(ee(n.abortSignal)){o.blocked.push({...p,reason:"absence re-verification aborted before it could run"});continue}if(await n.synthDeclaresTarget(c,g)===!0)continue;const O=f.targets.find(B=>B.logicalId===g);if(O===void 0||H(O,c)){o.blocked.push({...p,reason:"no probeable physical id on the covering operation"});continue}l.push({entry:p,target:O})}if(l.length===0)continue;const u=l.map(({entry:g,target:p})=>({stackName:c,logicalId:g.logicalId,resourceType:p.resourceType,physicalId:p.physicalId,failedStatus:"BY_NECESSITY_CONVERGE_CHECK",statusReason:"Absence re-verification before the converge deploy"})),k=await new te(n.aws,r?.probe).probeSuspects({stackName:c,suspects:u,...n.abortSignal!==void 0?{abortSignal:n.abortSignal}:{}});if(!k.success){for(const{entry:g}of l)o.blocked.push({...g,reason:`absence probe failed: ${k.error.message}`});continue}const b=new Map(k.data.map(g=>[g.logicalId,g]));for(const{entry:g}of l){const p=b.get(g.logicalId);if(p===void 0){o.blocked.push({...g,reason:"absence probe returned no verdict for this target"});continue}if(p.verdict==="confirmed-deleted"||p.verdict==="present"&&p.probeMethod===re){o.verifiedAbsent.push(g);continue}o.blocked.push({...g,reason:`${p.verdict}: ${p.verdictDetail}`})}}}return o}async function nt(n,t,s){const r=n.getAccountId(),d=n.getRegion(),a=[...t.stackNames];if(t.appName!==void 0){const o=new Set(a),c=await Pe(r,d,s?.journal);for(const e of c)e.verb==="recreate"&&e.appName===t.appName&&!o.has(e.stackName)&&(o.add(e.stackName),a.push(e.stackName))}for(const o of a){const c=await z(r,d,o,s?.journal);for(const e of c){const f=await De(n,{journal:e,...t.abortSignal!==void 0?{abortSignal:t.abortSignal}:{}},s);if(!f.success){const l=e.verb==="recreate"?`re-run 'fjall deploy${e.appName!==void 0?` ${e.appName}`:""}' to complete it`:`run 'fjall drift resume ${e.opId}' to complete it`;t.onLog?.(`Could not verify ${e.verb} operation ${e.opId} on ${o} after the deploy: ${_(f.error.message)} \u2014 ${l}`,"warn");continue}if(f.data.status==="done"&&e.verb==="recreate"){const l=f.data.journal,u=l.recreatedResources??[],i=new Set(u),k=new Set(l.orphanedResources??[]),b=new Set(l.unverifiedRemovals??[]),g=l.targets.map(w=>w.logicalId).filter(w=>!i.has(w)),p=g.filter(w=>!k.has(w)&&!b.has(w)),R=[];u.length>0&&R.push(`${u.join(", ")} verified live under the pinned names`),p.length>0&&R.push(`${p.join(", ")} removed instead of re-created (no longer declared)`),t.onLog?.(`Recreate operation ${e.opId} completed${R.length>0?` \u2014 ${R.join("; ")}`:""}`,"info");const O=g.filter(w=>k.has(w));if(O.length>0){const w=O.map(E=>{const F=l.targets.find(L=>L.logicalId===E)?.physicalId;return F!==void 0?`${E} [${F}]`:E}).join(", ");t.onLog?.(`Recreate operation ${e.opId}: ${w} left CloudFormation but the physical resource still exists outside CloudFormation \u2014 the consented deletion did not execute. Delete it via the owning service if it is no longer wanted.`,"warn")}const B=g.filter(w=>b.has(w));B.length>0&&t.onLog?.(`Recreate operation ${e.opId}: ${B.join(", ")} left CloudFormation but the physical deletion could not be verified \u2014 check the owning service before treating it as deleted.`,"warn")}else if(f.data.status==="done"){const l=f.data.journal.retainedByRedeclaration??[],u=new Set(l),i=f.data.journal.targets.map(b=>b.logicalId).filter(b=>!u.has(b)),k=i.length>0?`${o} no longer tracks ${i.join(", ")}${l.length>0?` (${l.join(", ")} retained by re-declaration)`:""}`:`${o} retained ${l.join(", ")} by re-declaration`;t.onLog?.(`Forget operation ${e.opId} completed \u2014 ${k}`,"info")}}}}export{Ze as assessForgetResumability,De as completeForgetAfterConverge,nt as completeForgetAfterDeploy,x as fetchLiveStackState,de as formatRecreateInFlightCures,rt as partitionPreFlightByRemediation,et as runForgetSurgery,tt as synthTemplateDeclaresResource,at as verifyByNecessityBeforeConverge};
|
|
1
|
+
var he=Object.defineProperty;var I=(a,e)=>he(a,"name",{value:e,configurable:!0});import{readFile as me}from"node:fs/promises";import{join as ye}from"node:path";import{CloudFormationClient as ve,DescribeStacksCommand as be,GetTemplateCommand as we}from"@aws-sdk/client-cloudformation";import{success as k,failure as O}from"@fjall/generator";import{getErrorMessage as z,maskSensitiveOutput as J}from"@fjall/util";import{logger as H}from"@fjall/util/logger";import{composeSdkAbortSignal as ee,isAborted as te}from"../../aws/organisations/types.js";import{isRemediationPlaceholderPhysicalId as U,remediationPhaseRank as B}from"../../types/remediation.js";import{DriftProbe as re,MEMBERSHIP_GATE_PROBE_METHOD as ae,probePhysicalPresence as ne}from"../drift/driftProbe.js";import{clearDriftSuspects as Ie,readDriftSuspects as $e,recordDriftSuspects as ke}from"../drift/driftSuspectJournal.js";import{captureRemediationForensics as Se}from"./forensicsCapture.js";import{findDeadReferencesFromTargets as Re,findTargetReferrers as Ne}from"./removalUpdate.js";import{archiveCompletedRemediationJournal as Te,computeRemediationOpId as oe,formatArchivedOperationNotice as Pe,formatCompletedOperationNotice as Ce,listActiveRemediationOps as V,listActiveRemediationOpsForContext as je,readRemediationJournal as ie,sweepExpiredRemediationJournals as Oe,writeRemediationJournal as se}from"./remediationJournal.js";import{applyRetainFlip as Ee,computeTemplateDigest as ce,deriveRetainFlipTemplate as Fe,findMarkedResourcesByOpId as Q,proveFlipMetadataOnly as Ae,readResourceTypes as Be,readRetainFlipState as x,verifyRetainFlipDiff as De,FORGET_MARKER_KEY as de,RECREATE_MARKER_KEY as Me}from"./retainFlip.js";const Y="ForgetResource";async function K(a,e,s){const r=a.getClient(ve);try{const d=await r.send(new we({StackName:e,TemplateStage:"Original"}),{abortSignal:ee(s)});if(d.TemplateBody===void 0||d.TemplateBody==="")return O(new Error(`GetTemplate returned an empty template body for ${e}`));const o=(await r.send(new be({StackName:e}),{abortSignal:ee(s)})).Stacks?.[0];return k({templateBody:d.TemplateBody,capabilities:o?.Capabilities??[],parameterKeys:(o?.Parameters??[]).flatMap(c=>c.ParameterKey!==void 0?[c.ParameterKey]:[])})}catch(d){return O(new Error(`Could not read the live state of ${e}: ${J(z(d))}`))}}I(K,"fetchLiveStackState");function le(a){const e=`'fjall deploy${a!==void 0?` ${a}`:""}'`;return`Two cures terminate it: re-apply the replacement-requiring configuration change and re-run ${e}, re-consenting when prompted \u2014 the recreate completes; or remove the resource from the fjall config and run ${e} \u2014 the operation closes as removed.`}I(le,"formatRecreateInFlightCures");async function rt(a,e,s){const r=e.targets.map(f=>f.logicalId),d=oe(e.stackName,r),n=await ie(a.getAccountId(),a.getRegion(),e.stackName,d,s?.journal);if(n!==null&&n.verb!=="forget"&&n.phase!=="done")return O(new Error(`Forget operation ${d} on ${e.stackName} collides with an in-flight '${n.verb}' operation covering the same resources \u2014 that surgery resumes only through its deploy. ${le(n.appName)} Nothing was mutated.`));if(n!==null&&n.phase!=="done")return k({opId:d,alreadyConsented:!0,source:"journal",journal:n,foreignMarkers:[]});const o=await K(a,e.stackName,e.abortSignal);if(!o.success)return o;const c=x(o.data.templateBody,r,e.stackName);if(!c.success)return c;const t=c.data.markerOpIds.filter(f=>f!==d).map(f=>{const l=Q(o.data.templateBody,f,e.stackName),u=l.success?l.data:[];return{opId:f,markedLogicalIds:u,markedResourceTypes:Be(o.data.templateBody,u,e.stackName)}});return c.data.markerOpIds.includes(d)?k({opId:d,alreadyConsented:!0,source:"marker",foreignMarkers:t}):k({opId:d,alreadyConsented:!1,source:"none",foreignMarkers:t})}I(rt,"assessForgetResumability");function pe(a,e,s,r,d){const n=r.filter(t=>t!==s);if(n.length===0)return k(void 0);const o=n.map(t=>{const f=Q(a,t,e);return`${t} covers ${f.success&&f.data.length>0?f.data.join(", "):"(could not recover its target set)"}`}),c=d?`resume this operation with 'fjall drift resume ${s}'`:"re-run 'fjall drift repair' to restart this operation";return O(new Error(`Forget operation ${s} on ${e} found target resources already carrying a ${de} marker from a different operation \u2014 its journal may live on another machine, and completing this operation over it would strand that surgery. Marked resources recovered from the live template: ${o.join("; ")}. Complete the foreign operation first \u2014 on the machine that holds its journal, run 'fjall drift resume ${n.join("' / 'fjall drift resume ")}' (or 'fjall drift repair' there if it is journal-less); once it converges, ${c}. Nothing was mutated.`))}I(pe,"guardForeignMarkers");async function W(a,e,s,r,d){const n=s.targets.map(i=>i.logicalId),o=x(e,n,s.stackName);if(!o.success)return o;if(B(s.phase)<B("flip-applied"))return k({absent:o.data.absentTargets,retained:[]});const c=Q(e,s.opId,s.stackName,s.verb==="recreate"?Me:de);if(!c.success)return c;const t=new Set(c.data),f=new Set(o.data.absentTargets),l=s.targets.filter(i=>!f.has(i.logicalId)&&!t.has(i.logicalId));if(l.length===0)return k({absent:o.data.absentTargets,retained:[]});const u=await fe(a,s,l,r,d);return k({absent:o.data.absentTargets,retained:l.map(i=>i.logicalId).filter(i=>u.has(i))})}I(W,"resolveConvergedTargets");async function fe(a,e,s,r,d){const n=s.map(t=>({stackName:e.stackName,logicalId:t.logicalId,resourceType:t.resourceType,...U(t,e.stackName)?{}:{physicalId:t.physicalId},failedStatus:e.verb==="recreate"?"RECREATE_CLOSURE_CHECK":"FORGET_CLOSURE_CHECK",statusReason:`Presence verification at ${e.verb} closure`})),c=await new re(a,d?.probe).probeSuspects({stackName:e.stackName,suspects:n,...r!==void 0?{abortSignal:r}:{}});return c.success?new Set(c.data.filter(t=>t.verdict==="present"&&t.probeMethod!==ae).map(t=>t.logicalId)):(H.debug(Y,"Closure probe failed \u2014 failing closed (not retained)",{stackName:e.stackName,opId:e.opId,error:c.error.message}),new Set)}I(fe,"probeClosureCandidates");async function at(a,e,s){const{stackName:r,targets:d,onProgress:n,abortSignal:o}=e;if(d.length===0)return O(new Error("Forget surgery requires at least one target"));const c=s?.now??(()=>new Date),t=a.getAccountId(),f=a.getRegion(),l=d.map(h=>h.logicalId),u=oe(r,l);let i=await ie(t,f,r,u,s?.journal);if(i!==null&&i.verb!=="forget"&&i.phase!=="done")return O(new Error(`Forget surgery ${u} on ${r} collides with an in-flight '${i.verb}' operation covering the same resources \u2014 that surgery resumes only through its deploy. ${le(i.appName)} Nothing was mutated.`));if(i!==null&&i.phase==="done"){if(e.doneJournalPolicy!=="archive-and-restart")return n?.(Ce(i)),k({opId:u,phase:"done",convergePending:!1,...i.forensicsPath!==void 0?{forensicsPath:i.forensicsPath}:{},journal:i});const h=await Te(i,s?.journal);if(!h.success)return h;n?.(Pe(i)),i=null}const S=I(async h=>{const T=await se(h,s?.journal);return T.success?k(h):O(T.error)},"persistJournal");let b;if(i===null){const h=await K(a,r,o);if(!h.success)return h;const T=x(h.data.templateBody,l,r);if(!T.success)return T;const M=pe(h.data.templateBody,r,u,T.data.markerOpIds,!1);if(!M.success)return M;if(T.data.markerOpIds.includes(u)){n?.(`Recognised in-flight forget markers on ${r} \u2014 resuming from the fjall:forget marker`);const m=c().toISOString(),P=await S({version:1,opId:u,verb:"forget",accountId:t,region:f,stackName:r,...e.appName!==void 0?{appName:e.appName}:{},targets:[...d],phase:T.data.allFlipped?"flip-applied":"captured",createdAt:m,updatedAt:m});if(!P.success)return P;i=P.data,b=h.data}else{n?.(`Capturing forensics for ${r} (${u})`);const m=await Se(a,{stackName:r,opId:u,findings:e.findings??[],...o!==void 0?{abortSignal:o}:{}},s?.forensics);if(!m.success)return m;const P=c().toISOString(),$=await S({version:1,opId:u,verb:"forget",accountId:t,region:f,stackName:r,...e.appName!==void 0?{appName:e.appName}:{},targets:[...d],phase:"captured",createdAt:P,updatedAt:P,forensicsPath:m.data.forensicsPath,...e.consentedPhysicalNames!==void 0?{consentedPhysicalNames:[...e.consentedPhysicalNames]}:{}});if(!$.success)return $;i=$.data,b={templateBody:m.data.liveTemplateBody,capabilities:m.data.capabilities,parameterKeys:m.data.parameterKeys}}}if(b===void 0){const h=await K(a,r,o);if(!h.success)return h;b=h.data}let g=b.templateBody,p=x(g,l,r);if(!p.success)return p;const N=pe(g,r,u,p.data.markerOpIds,!0);if(!N.success)return N;const E=i.phase,D=await W(a,g,i,o,s);if(!D.success)return D;const w=new Set(D.data.retained),F=p.data.policyPendingTargets.filter(h=>!w.has(h)),A=[];let G=!1;if(F.length>0){const h=Ne(g,F,r);if(!h.success)return h;if(h.data.length>0){const y=F.length>1;return O(new Error(`Forget surgery cannot proceed on ${r}: ${F.join(", ")} must have DeletionPolicy changed to Retain before removal, but other resources in the stack still reference the deleted resource${y?"s":""} (${h.data.join(", ")}) \u2014 CloudFormation re-resolves references to a deleted resource during any update that modifies it, so the policy change cannot be applied. Either restore the deleted resource${y?"s":""} out-of-band and re-run 'fjall drift detect' (the finding clears; this operation record then idles until the 90-day retention sweep), or remove the target and every referencing resource from the stack in one manual template update \u2014 they cannot outlive it. This shape is a recorded fjall limitation (drift-remediation ADR, 2026-07-12). Nothing was mutated.`))}const T=l.filter(y=>!w.has(y)),M=Re(g,F,T,r);if(!M.success)return M;let m=M.data;if(m.length>0){const y=new Set(m.flatMap(v=>v.deadReferences)),R=await fe(a,i,i.targets.filter(v=>y.has(v.logicalId)),o,s);R.size>0&&(m=m.map(v=>({...v,deadReferences:v.deadReferences.filter(C=>!R.has(C))})).filter(v=>v.deadReferences.length>0))}if(m.length>0){const y=[];for(const R of m){const v=i.targets.find(j=>j.logicalId===R.pendingTarget);if(v===void 0||U(v,r)){y.push(`${R.pendingTarget} (no probeable physical id on this operation)`);continue}const C=await ne(a,{resourceType:v.resourceType,physicalId:v.physicalId,...o!==void 0?{abortSignal:o}:{}});C.verdict==="confirmed-deleted"?A.push(R.pendingTarget):y.push(`${R.pendingTarget} (${C.verdict}: ${C.detail})`)}if(y.length>0){const R=m.map(j=>`${j.pendingTarget} itself references the deleted ${j.deadReferences.join(", ")}`).join("; "),v=new Set(m.flatMap(j=>j.deadReferences)).size>1,C=m.length>1;return O(new Error(`Forget surgery cannot proceed on ${r}: ${m.map(j=>j.pendingTarget).join(", ")} must have DeletionPolicy changed to Retain before removal, but ${R} \u2014 CloudFormation resolves a modified resource's own references at execution time, and a reference to a deleted resource cannot resolve, so the policy change cannot be applied. fjall proceeds without the policy change only when the referencing target is verified deleted against its owning service (the converge's removal is then a physical no-op), and verification did not confirm deletion for ${y.join("; ")}. Either restore the referenced resource${v?"s":""} out-of-band and resume this operation (the policy change can then apply), or remove the referencing target${C?"s":""} and every reference to ${C?"them":"it"} (including stack Outputs) from the stack in one manual template update \u2014 CloudFormation deletes template-removed resources from its stored state, so removing an already-deleted resource succeeds where modifying it cannot. The unverified-absence shape is a recorded fjall limitation (drift-remediation ADR, 2026-07-12). Nothing was mutated.`))}n?.(`${A.join(", ")} cannot carry the Retain flip (${m.map(R=>`${R.pendingTarget} references the deleted ${R.deadReferences.join(", ")}`).join("; ")}) but ${A.length===1?"is":"are"} verified deleted against the owning service \u2014 proceeding without the flip: the converge's removal of an already-deleted resource is a physical no-op (flipless by necessity)`)}const P=new Set(A),$=F.filter(y=>!P.has(y));if($.length>0){G=!0,i.liveTemplateDigest!==void 0&&i.liveTemplateDigest!==ce(g)&&B(i.phase)>=B("flip-verified")&&n?.(`Live template of ${r} changed since the flip was verified \u2014 re-verifying from the current template`),n?.(`Deriving the retain flip for ${r}`);const y=Fe({liveTemplateBody:g,stackName:r,targets:$,opId:u,at:c().toISOString()});if(!y.success)return y;const R=De(g,y.data.flipTemplateBody,$,r);if(!R.success)return R;n?.("Proving the flip is metadata-only (review change set)");const v=await Ae(a,{stackName:r,flipTemplateBody:y.data.flipTemplateBody,targets:$,parameterKeys:b.parameterKeys,...o!==void 0?{abortSignal:o}:{}},s?.flip);if(!v.success)return v;const C=await S({...i,phase:"flip-verified",updatedAt:c().toISOString(),liveTemplateDigest:y.data.liveTemplateDigest,proof:{diffGuardPassed:!0,changeSetMetadataOnly:!0,noChanges:v.data.noChanges,provenAt:c().toISOString()}});if(!C.success)return C;i=C.data,n?.(`Applying the retain flip to ${r}`);const j=await Ee(a,{stackName:r,flipTemplateBody:y.data.flipTemplateBody,capabilities:b.capabilities,parameterKeys:b.parameterKeys,derivedFromTemplateDigest:y.data.liveTemplateDigest,opId:u,...o!==void 0?{abortSignal:o}:{}},s?.flip);if(!j.success)return j;const q=await K(a,r,o);if(!q.success)return q;if(g=q.data.templateBody,p=x(g,l,r),!p.success)return p;const ge=new Set($),X=p.data.unflippedTargets.filter(Z=>ge.has(Z)&&!w.has(Z));if(X.length>0)return O(new Error(`Retain flip on ${r} reported ${j.data.outcome} but GetTemplate does not show Retain + the fjall:forget marker on ${X.join(", ")} \u2014 aborting to manual repair.`))}}if(B(i.phase)<B("flip-applied")){const h=new Set(A),T=p.data.policyPendingTargets.filter($=>!w.has($)&&!h.has($));if(T.length>0)return O(new Error(`Retain flip on ${r} could not be recorded: ${T.join(", ")} do not carry the Retain deletion policies in the live template \u2014 re-run 'fjall drift repair' to re-verify.`));const M=new Set(p.data.absentTargets),m=p.data.unflippedTargets.filter($=>!M.has($)&&!w.has($)&&!h.has($));m.length>0&&n?.(`${m.join(", ")} already ${m.length===1?"carries":"carry"} DeletionPolicy Retain in the live template \u2014 no stack update needed (flipless fast-path)`);const P=await S({...i,phase:"flip-applied",updatedAt:c().toISOString(),liveTemplateDigest:ce(g),...m.length>0?{fliplessTargets:m}:{},...A.length>0?{fliplessByNecessity:A}:{}});if(!P.success)return P;i=P.data}const L=!G&&i.phase===E?D:await W(a,g,i,o,s);if(!L.success)return L;const _=L.data.retained;if(L.data.absent.length+_.length===l.length){const h=await ue(i,_,c,s?.journal,s?.driftJournal);return h.success?(i=h.data,n?.(_.length>0?`Forget operation ${u} complete \u2014 the converge kept ${_.join(", ")} (re-declared in the synthesised template)${L.data.absent.length>0?` and ${r} no longer tracks the remaining targets`:""}`:`Targets are no longer in the ${r} template \u2014 forget operation ${u} complete`),k({opId:u,phase:"done",convergePending:!1,...i.forensicsPath!==void 0?{forensicsPath:i.forensicsPath}:{},journal:i})):h}return n?.(`Retain flip is live on ${r} \u2014 run the converge deploy to complete the forget`),k({opId:u,phase:i.phase,convergePending:!0,...i.forensicsPath!==void 0?{forensicsPath:i.forensicsPath}:{},journal:i})}I(at,"runForgetSurgery");async function xe(a,e,s){const{journal:r}=e;if(r.phase==="done")return k({status:"done",journal:r});const d=s?.now??(()=>new Date),n=r.targets.map(l=>l.logicalId),o=await K(a,r.stackName,e.abortSignal);if(!o.success)return o;const c=await W(a,o.data.templateBody,r,e.abortSignal,s);if(!c.success)return c;if(c.data.absent.length+c.data.retained.length!==n.length){const l=new Set([...c.data.absent,...c.data.retained]);return k({status:"converge-pending",journal:r,absent:[...c.data.absent],retained:[...c.data.retained],unverified:n.filter(u=>!l.has(u))})}const t=r.verb==="recreate"&&c.data.absent.length>0?await Ke(a,r,c.data.absent,e.abortSignal):void 0,f=await ue(r,c.data.retained,d,s?.journal,s?.driftJournal,t);return f.success?k({status:"done",journal:f.data}):f}I(xe,"completeForgetAfterConverge");async function Ke(a,e,s,r){const d={orphaned:[],unverified:[]};for(const n of s){const o=e.targets.find(t=>t.logicalId===n);if(o===void 0)continue;if(U(o,e.stackName)){d.unverified.push(n);continue}const c=await ne(a,{resourceType:o.resourceType,physicalId:o.physicalId,...r!==void 0?{abortSignal:r}:{}});c.verdict==="present"?d.orphaned.push(n):c.verdict==="unconfirmed"&&d.unverified.push(n)}return d}I(Ke,"probeAbsentClosureTargets");async function ue(a,e,s,r,d,n){const o=s().toISOString(),c={...a,phase:"done",updatedAt:o,completedAt:o,...e.length>0?a.verb==="recreate"?{recreatedResources:[...e]}:{retainedByRedeclaration:[...e]}:{},...n!==void 0&&n.orphaned.length>0?{orphanedResources:[...n.orphaned]}:{},...n!==void 0&&n.unverified.length>0?{unverifiedRemovals:[...n.unverified]}:{}},t=await se(c,r);return t.success?(await Le(a,d).catch(f=>{H.warn(Y,"Failed to clear drift suspects after forget",{stackName:a.stackName,opId:a.opId,error:J(z(f))})}),k(c)):O(t.error)}I(ue,"finaliseForgetJournal");async function Le(a,e){const s=await $e(a.accountId,a.region,a.stackName,e);if(s===null)return;const r=new Set(a.targets.map(n=>n.logicalId)),d=s.suspects.filter(n=>!r.has(n.logicalId));if(d.length!==s.suspects.length){if(d.length===0){await Ie(a.accountId,a.region,a.stackName,e);return}await ke({...s,suspects:d},e)}}I(Le,"clearForgottenDriftSuspects");async function nt(a,e,s){try{const r=await me(ye(a,`${e}.template.json`),"utf8"),d=JSON.parse(r);return typeof d.Resources=="object"&&d.Resources!==null&&Object.hasOwn(d.Resources,s)}catch(r){H.debug(Y,"Could not read the synthesised template",{stackName:e,error:J(z(r))});return}}I(nt,"synthTemplateDeclaresResource");async function ot(a,e,s){await Oe(s?.journal,s?.now);const r=a.aws.getAccountId(),d=a.aws.getRegion(),n={blocked:[],inProgressOpIds:[],converging:[],flipNotLive:[],flipUnverified:[],stillDeclared:[],recreateConverging:[]},o=new Map,c=new Map;for(const t of e){if(te(a.abortSignal)){n.blocked.push(t);continue}let f=o.get(t.stackName);f===void 0&&(f=await V(r,d,t.stackName,s?.journal),o.set(t.stackName,f));const l=f.find(p=>p.verb==="forget"&&p.targets.some(N=>N.logicalId===t.logicalId));if(l===void 0){const p=f.find(N=>N.verb==="recreate"&&N.targets.some(E=>E.logicalId===t.logicalId));if(p!==void 0){n.recreateConverging.push({finding:t,opId:p.opId,...p.appName!==void 0?{appName:p.appName}:{}});continue}n.blocked.push(t);continue}if(B(l.phase)<B("flip-applied")){n.blocked.push(t),n.inProgressOpIds.includes(l.opId)||n.inProgressOpIds.push(l.opId);continue}let u=c.get(t.stackName);if(u===void 0){const p=await K(a.aws,t.stackName,a.abortSignal);p.success?u={templateBody:p.data.templateBody}:(H.debug(Y,"Could not read the live template for the [G2] partition",{stackName:t.stackName,error:p.error.message}),u={errorMessage:p.error.message}),c.set(t.stackName,u)}if("errorMessage"in u){n.blocked.push(t),n.flipUnverified.push({finding:t,opId:l.opId,message:u.errorMessage});continue}const i=x(u.templateBody,[t.logicalId],t.stackName),S=new Set(l.fliplessByNecessity??[]);if(!(i.success&&(i.data.absentTargets.length===1||(i.data.policyPendingTargets.length===0||S.has(t.logicalId))&&i.data.markerOpIds.every(p=>p===l.opId)))){n.blocked.push(t),n.flipNotLive.push({finding:t,opId:l.opId});continue}if(await a.synthDeclaresTarget(t.stackName,t.logicalId)===!0){n.blocked.push(t),n.stillDeclared.push({finding:t,opId:l.opId});continue}n.converging.push({finding:t,opId:l.opId})}return n}I(ot,"partitionPreFlightByRemediation");async function it(a,e,s,r){const d=a.aws.getAccountId(),n=a.aws.getRegion(),o={verifiedAbsent:[],blocked:[]};for(const c of e){const t=await V(d,n,c,r?.journal);for(const f of t){if(f.verb!=="forget"||B(f.phase)<B("flip-applied"))continue;const l=[];for(const g of f.fliplessByNecessity??[]){const p={stackName:c,logicalId:g,opId:f.opId};if(s.get(c)?.has(g)===!0){o.verifiedAbsent.push(p);continue}if(te(a.abortSignal)){o.blocked.push({...p,reason:"absence re-verification aborted before it could run"});continue}if(await a.synthDeclaresTarget(c,g)===!0)continue;const E=f.targets.find(D=>D.logicalId===g);if(E===void 0||U(E,c)){o.blocked.push({...p,reason:"no probeable physical id on the covering operation"});continue}l.push({entry:p,target:E})}if(l.length===0)continue;const u=l.map(({entry:g,target:p})=>({stackName:c,logicalId:g.logicalId,resourceType:p.resourceType,physicalId:p.physicalId,failedStatus:"BY_NECESSITY_CONVERGE_CHECK",statusReason:"Absence re-verification before the converge deploy"})),S=await new re(a.aws,r?.probe).probeSuspects({stackName:c,suspects:u,...a.abortSignal!==void 0?{abortSignal:a.abortSignal}:{}});if(!S.success){for(const{entry:g}of l)o.blocked.push({...g,reason:`absence probe failed: ${S.error.message}`});continue}const b=new Map(S.data.map(g=>[g.logicalId,g]));for(const{entry:g}of l){const p=b.get(g.logicalId);if(p===void 0){o.blocked.push({...g,reason:"absence probe returned no verdict for this target"});continue}if(p.verdict==="confirmed-deleted"||p.verdict==="present"&&p.probeMethod===ae){o.verifiedAbsent.push(g);continue}o.blocked.push({...g,reason:`${p.verdict}: ${p.verdictDetail}`})}}}return o}I(it,"verifyByNecessityBeforeConverge");async function st(a,e,s){const r=a.getAccountId(),d=a.getRegion(),n=[...e.stackNames];if(e.appName!==void 0){const o=new Set(n),c=await je(r,d,s?.journal);for(const t of c)t.verb==="recreate"&&t.appName===e.appName&&!o.has(t.stackName)&&(o.add(t.stackName),n.push(t.stackName))}for(const o of n){const c=await V(r,d,o,s?.journal);for(const t of c){const f=await xe(a,{journal:t,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}},s);if(!f.success){const l=t.verb==="recreate"?`re-run 'fjall deploy${t.appName!==void 0?` ${t.appName}`:""}' to complete it`:`run 'fjall drift resume ${t.opId}' to complete it`;e.onLog?.(`Could not verify ${t.verb} operation ${t.opId} on ${o} after the deploy: ${J(f.error.message)} \u2014 ${l}`,"warn");continue}if(f.data.status==="done"&&t.verb==="recreate"){const l=f.data.journal,u=l.recreatedResources??[],i=new Set(u),S=new Set(l.orphanedResources??[]),b=new Set(l.unverifiedRemovals??[]),g=l.targets.map(w=>w.logicalId).filter(w=>!i.has(w)),p=g.filter(w=>!S.has(w)&&!b.has(w)),N=[];u.length>0&&N.push(`${u.join(", ")} verified live under the pinned names`),p.length>0&&N.push(`${p.join(", ")} removed instead of re-created (no longer declared)`),e.onLog?.(`Recreate operation ${t.opId} completed${N.length>0?` \u2014 ${N.join("; ")}`:""}`,"info");const E=g.filter(w=>S.has(w));if(E.length>0){const w=E.map(F=>{const A=l.targets.find(G=>G.logicalId===F)?.physicalId;return A!==void 0?`${F} [${A}]`:F}).join(", ");e.onLog?.(`Recreate operation ${t.opId}: ${w} left CloudFormation but the physical resource still exists outside CloudFormation \u2014 the consented deletion did not execute. Delete it via the owning service if it is no longer wanted.`,"warn")}const D=g.filter(w=>b.has(w));D.length>0&&e.onLog?.(`Recreate operation ${t.opId}: ${D.join(", ")} left CloudFormation but the physical deletion could not be verified \u2014 check the owning service before treating it as deleted.`,"warn")}else if(f.data.status==="done"){const l=f.data.journal.retainedByRedeclaration??[],u=new Set(l),i=f.data.journal.targets.map(b=>b.logicalId).filter(b=>!u.has(b)),S=i.length>0?`${o} no longer tracks ${i.join(", ")}${l.length>0?` (${l.join(", ")} retained by re-declaration)`:""}`:`${o} retained ${l.join(", ")} by re-declaration`;e.onLog?.(`Forget operation ${t.opId} completed \u2014 ${S}`,"info")}}}}I(st,"completeForgetAfterDeploy");export{rt as assessForgetResumability,xe as completeForgetAfterConverge,st as completeForgetAfterDeploy,K as fetchLiveStackState,le as formatRecreateInFlightCures,ot as partitionPreFlightByRemediation,at as runForgetSurgery,nt as synthTemplateDeclaresResource,it as verifyByNecessityBeforeConverge};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{CloudFormationClient as
|
|
1
|
+
var B=Object.defineProperty;var a=(e,n)=>B(e,"name",{value:n,configurable:!0});import{CloudFormationClient as O,DescribeStackResourceCommand as T}from"@aws-sdk/client-cloudformation";import{DescribeDBClustersCommand as L,DescribeDBInstancesCommand as A,RDSClient as k}from"@aws-sdk/client-rds";import{success as b,failure as g}from"@fjall/generator";import{listResources as j,modifyResource as x}from"@fjall/generator/codemod";import{getErrorMessage as D,maskSensitiveOutput as C}from"@fjall/util";import{logger as F}from"@fjall/util/logger";import{composeSdkAbortSignal as S,isAborted as N}from"../../aws/organisations/types.js";import{remediationPlaceholderPhysicalId as V}from"../../types/remediation.js";const W="PinRemediation",v="/properties/";async function _(e,n,o){try{const i=(await e.getClient(k).send(new A({DBInstanceIdentifier:n}),{abortSignal:S(o)})).DBInstances?.[0]?.Endpoint?.Port;return i===void 0?g(new Error(`rds:DescribeDBInstances returned no endpoint port for '${n}'`)):b(i)}catch(t){return g(new Error(C(D(t))))}}a(_,"probeDbInstancePort");async function G(e,n,o){try{const i=(await e.getClient(k).send(new L({DBClusterIdentifier:n}),{abortSignal:S(o)})).DBClusters?.[0]?.Port;return i===void 0?g(new Error(`rds:DescribeDBClusters returned no port for '${n}'`)):b(i)}catch(t){return g(new Error(C(D(t))))}}a(G,"probeDbClusterPort");const E={"AWS::RDS::DBInstance#Port":{configProperty:"port",constructType:"database",method:"rds:DescribeDBInstances",probe:_},"AWS::RDS::DBCluster#Port":{configProperty:"port",constructType:"database",method:"rds:DescribeDBClusters",probe:G}};function w(e){return e.startsWith(v)?e.slice(v.length):e}a(w,"normaliseOffendingProperty");function te(e,n){if(n.length===0)return{covered:!1,unsupportedPairs:[`${e} (no offending properties recorded)`]};const o=n.map(w).filter(t=>E[`${e}#${t}`]===void 0).map(t=>`${e}#${t}`);return{covered:o.length===0,unsupportedPairs:o}}a(te,"pinProbeCoverage");function H(e){return typeof e=="number"?String(e):JSON.stringify(e)}a(H,"formatConfigValue");async function J(e,n,o){const t=V(n.stack,n.logicalId);if(n.physicalName!==t)return b(n.physicalName);try{const s=(await e.getClient(O).send(new T({StackName:n.stack,LogicalResourceId:n.logicalId}),{abortSignal:S(o)})).StackResourceDetail?.PhysicalResourceId;return s===void 0||s===""?g(new Error(`cfn:DescribeStackResource resolved no physical id for ${n.stack}/${n.logicalId}`)):b(s)}catch(r){return g(new Error(C(D(r))))}}a(J,"resolveProbePhysicalId");function M(e,n,o){const t=j(e,n);if(!t.success)return{detail:`config parse failed: ${t.error.kind}`};const r=t.data.resources.filter(i=>i.type===o);return r.length===1&&r[0]!==void 0?{constructName:r[0].name}:r.length===0?{detail:`no ${o} construct found in the config`}:{candidates:r.map(i=>i.name),detail:`${r.length} ${o} constructs found \u2014 cannot pin unambiguously`}}a(M,"resolveSingleConstruct");function X(e){if(e.outcome==="edit-ready")return`pinned ${e.pinned.map(r=>r.configLine).join(", ")} on construct '${e.constructName}' \u2014 edit ready`;const n=e.detail!==void 0?` \u2014 ${e.detail}`:"",o=e.candidates!==void 0&&e.candidates.length>0?`; candidate constructs: ${e.candidates.join(", ")}`:"",t=e.reason!=="already-pinned"&&e.pinned!==void 0&&e.pinned.length>0?`; apply by hand: ${e.pinned.map(r=>r.configLine).join(", ")}`:"";return`report-only (${e.reason})${n}${o}${t}`}a(X,"formatPinOutcomeDetail");function re(e){return e.outcomes.map(n=>`${n.physicalName}: ${X(n)}`)}a(re,"renderPinReportLines");async function oe(e,n){const o=n.infrastructureFilePath??"infrastructure.ts",t=[];let r=n.infrastructureContent,i=!1;for(const s of n.targets){if(N(n.abortSignal)){F.debug(W,"Aborted mid-loop \u2014 remaining targets not probed",{remaining:n.targets.length-t.length});break}const u={stack:s.stack,logicalId:s.logicalId,resourceType:s.resourceType,physicalName:s.physicalName},m=s.offendingProperties.map(w).map(c=>({property:c,spec:E[`${s.resourceType}#${c}`]})),$=m.filter(c=>c.spec!==void 0);if(m.length===0||$.length<m.length){const c=m.filter(l=>l.spec===void 0),f=m.length===0?`${s.resourceType} (no offending properties recorded)`:c.map(l=>`${s.resourceType}#${l.property}`).join(", ");t.push({...u,outcome:"report-only",reason:"no-probe-support",detail:`no live-value probe for ${f} in this release`});continue}const P=await J(e,s,n.abortSignal);if(!P.success){t.push({...u,outcome:"report-only",reason:"physical-id-unresolved",detail:P.error.message});continue}const d=[];let y;for(const{property:c,spec:f}of $){if(N(n.abortSignal)){y="aborted before probing completed";break}const l=await f.probe(e,P.data,n.abortSignal);if(!l.success){y=`${f.method}: ${l.error.message}`;break}d.push({property:c,configProperty:f.configProperty,liveValue:l.data,configLine:`${f.configProperty}: ${H(l.data)}`})}if(y!==void 0){t.push({...u,outcome:"report-only",reason:"probe-failed",detail:y,...d.length>0?{pinned:d}:{}});continue}if(r===void 0){t.push({...u,outcome:"report-only",reason:"no-infrastructure-content",detail:"no infrastructure config available on this surface \u2014 apply the config line(s) by hand",pinned:d});continue}const I=$[0]?.spec.constructType??"database",p=M(r,o,I);if(p.constructName===void 0){t.push({...u,outcome:"report-only",reason:"unresolved-construct",...p.detail!==void 0?{detail:p.detail}:{},...p.candidates!==void 0?{candidates:p.candidates}:{},pinned:d});continue}const R={};for(const c of d)R[c.configProperty]=c.liveValue;const h=x(r,{type:I,name:p.constructName,properties:R,filePath:o});if(!h.success){t.push({...u,outcome:"report-only",reason:"edit-failed",detail:`codemod failed: ${h.error.kind}`,pinned:d});continue}if(h.data.content===r){t.push({...u,outcome:"report-only",reason:"already-pinned",detail:"the config already contains the probed value(s) \u2014 no edit needed; if the deploy still plans a replacement, the drift lies outside these properties",pinned:d});continue}r=h.data.content,i=!0,t.push({...u,outcome:"edit-ready",constructName:p.constructName,pinned:d})}return{outcomes:t,...i&&r!==void 0&&n.infrastructureContent!==void 0?{modifiedContent:r,sourceContent:n.infrastructureContent}:{}}}a(oe,"runPinRemediation");export{X as formatPinOutcomeDetail,te as pinProbeCoverage,re as renderPinReportLines,oe as runPinRemediation};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import{getErrorMessage as
|
|
1
|
+
var g=Object.defineProperty;var c=(r,e)=>g(r,"name",{value:e,configurable:!0});import{getErrorMessage as h,maskSensitiveOutput as $}from"@fjall/util";import{logger as N}from"@fjall/util/logger";import{DriftProbe as v}from"../drift/driftProbe.js";import{runDriftPreFlight as y}from"../drift/driftPreFlight.js";import{formatRecreateInFlightCures as I}from"./forgetResource.js";import{listActiveRemediationOps as b}from"./remediationJournal.js";const k="PostFailureRepairOffer";class R extends Error{static{c(this,"DriftRepairAvailableError")}findings;constructor(e,t){super(e),this.name="DriftRepairAvailableError",this.findings=t}}function E(r,e,t){const i=r.map(n=>` ${n.stackName} \u203A ${n.logicalId} (${n.resourceType})${n.physicalId!==void 0?` [${n.physicalId}]`:""} \u2014 ${n.verdictDetail}`),o=t!==void 0&&t!==""?t:e!==void 0&&e!==""?e:"<target>";return`Out-of-band deletion CONFIRMED \u2014 CloudFormation still tracks ${r.length} deleted resource(s):
|
|
2
2
|
${i.join(`
|
|
3
3
|
`)}
|
|
4
|
-
Repair in this session: fjall drift repair ${
|
|
5
|
-
(names are exact and case-sensitive; 'fjall drift detect' shows the full report)`}function
|
|
6
|
-
${
|
|
4
|
+
Repair in this session: fjall drift repair ${o} --remediate <physicalName>=forget
|
|
5
|
+
(names are exact and case-sensitive; 'fjall drift detect' shows the full report)`}c(E,"formatDriftRepairOffer");function p(r,e){const t=r.map(({finding:o,opId:n})=>` ${o.stackName} \u203A ${o.logicalId} (${o.resourceType})${o.physicalId!==void 0?` [${o.physicalId}]`:""} \u2014 covered by in-flight recreate remediation ${n}`),i=r.find(o=>o.appName!==void 0)?.appName??e;return`${r.length} deleted resource(s) are already covered by an in-flight recreate remediation:
|
|
6
|
+
${t.join(`
|
|
7
7
|
`)}
|
|
8
|
-
Do not use '--remediate <physicalName>=forget' for these \u2014 the forget arm fails closed on the in-flight recreate journal. ${
|
|
8
|
+
Do not use '--remediate <physicalName>=forget' for these \u2014 the forget arm fails closed on the in-flight recreate journal. ${I(i)}`}c(p,"formatRecreateCoveredNotice");async function P(r,e){try{const t=await y({aws:r,probe:new v(r),...e.journal!==void 0?{journal:e.journal}:{}},{stackNames:e.stackNames,cfnService:e.cfnService,...e.abortSignal!==void 0?{abortSignal:e.abortSignal}:{}});if(!t.success||t.data.confirmed.length===0)return e.originalError;const{covered:i,uncovered:o}=await O(r,t.data.confirmed,e.remediationJournal);if(o.length===0)return new Error(`${e.originalError.message}
|
|
9
9
|
|
|
10
|
-
${
|
|
10
|
+
${p(i,e.appName)}`);const n=i.length>0?`
|
|
11
11
|
|
|
12
|
-
${
|
|
12
|
+
${p(i,e.appName)}`:"";return new R(`${e.originalError.message}
|
|
13
13
|
|
|
14
|
-
${
|
|
14
|
+
${E(o,e.appName,e.repairTarget)}${n}`,o)}catch(t){return N.debug(k,"Post-failure repair probe threw \u2014 offer skipped",{stackNames:[...e.stackNames],error:$(h(t))}),e.originalError}}c(P,"composePostFailureRepairOffer");async function O(r,e,t){const i=r.getAccountId(),o=r.getRegion(),n=new Map,d=[],f=[];for(const a of e){let s=n.get(a.stackName);s===void 0&&(s=await b(i,o,a.stackName,t),n.set(a.stackName,s));const l=s.find(u=>u.verb==="recreate"&&u.targets.some(m=>m.logicalId===a.logicalId));l!==void 0?d.push({finding:a,opId:l.opId,...l.appName!==void 0?{appName:l.appName}:{}}):f.push(a)}return{covered:d,uncovered:f}}c(O,"partitionByRecreateCoverage");export{R as DriftRepairAvailableError,P as composePostFailureRepairOffer,E as formatDriftRepairOffer,p as formatRecreateCoveredNotice};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{CloudFormationClient as
|
|
1
|
+
var g=Object.defineProperty;var u=(s,r)=>g(s,"name",{value:r,configurable:!0});import{CloudFormationClient as w,ListImportsCommand as y}from"@aws-sdk/client-cloudformation";import{success as p,failure as i}from"@fjall/generator";import{getErrorMessage as m,maskSensitiveOutput as $}from"@fjall/util";import{logger as b}from"@fjall/util/logger";import{composeSdkAbortSignal as E,isAborted as h}from"../../aws/organisations/types.js";import{isRemediationPlaceholderPhysicalId as N}from"../../types/remediation.js";import{DELETE_BLOCK_PROBES as x}from"../probes/deleteBlockProbes.js";import{fetchLiveStackState as I}from"./forgetResource.js";import{deriveRemovalTemplate as R}from"./removalUpdate.js";const k="RecreatePreFlight",v=/is not imported by any stack/i;async function S(s,r,t){const d=s.getClient(w),l=[];let a;try{do{if(h(t))return i(new Error(`aborted while listing imports of export '${r}'`));const o=await d.send(new y({ExportName:r,...a!==void 0?{NextToken:a}:{}}),{abortSignal:E(t)});l.push(...o.Imports??[]),a=o.NextToken}while(a!==void 0);return p(l)}catch(o){return v.test(m(o))?p([]):i(new Error($(m(o))))}}u(S,"listExportImporters");async function D(s,r){for(const{stackName:t,targets:d}of r.stacks){if(h(r.abortSignal))return i(new Error(`Recreate pre-flight aborted before ${t} was checked \u2014 nothing was mutated in this run.`));const l=d.map(e=>e.logicalId),a=await I(s,t,r.abortSignal);if(!a.success)return i(new Error(`${a.error.message} Nothing was mutated in this run.`));const o=R(a.data.templateBody,l,t);if(!o.success)return i(new Error(`${o.error.message} Nothing was mutated in this run.`));const f=new Set(o.data.alreadyAbsent);for(const e of o.data.strippedExports){if(e.exportName===void 0)return i(new Error(`Recreate pre-flight for ${t}: removing ${l.join(", ")} strips output '${e.outputName}' whose Export name is not a literal string, so its cross-stack consumers cannot be verified \u2014 perform the replacement manually. Nothing was mutated in this run.`));const n=await S(s,e.exportName,r.abortSignal);if(!n.success)return i(new Error(`Recreate pre-flight for ${t}: could not verify whether export '${e.exportName}' (output '${e.outputName}') is imported by other stacks: ${n.error.message}. Retry the deploy. Nothing was mutated in this run.`));if(n.data.length>0)return i(new Error(`Recreate pre-flight for ${t}: removing ${l.join(", ")} strips export '${e.exportName}' (output '${e.outputName}'), which is imported by ${n.data.join(", ")} \u2014 CloudFormation refuses to drop an export while imports exist, so the surgery would fail mid-deploy. Redeploy the importing stack(s) without the import first, or perform the replacement manually. Nothing was mutated in this run.`))}for(const e of d){if(f.has(e.logicalId))continue;const n=x[e.resourceType];if(n===void 0)continue;if(N(e,t)){b.debug(k,"No live physical id to probe \u2014 skipping",{stackName:t,logicalId:e.logicalId});continue}const c=await n.probe(s,e.physicalId,r.abortSignal);if(!c.success)return i(new Error(`Recreate pre-flight for ${t}: could not verify that ${e.physicalId} (${e.logicalId}) is deletable \u2014 ${n.method} failed: ${c.error.message}. Retry the deploy. Nothing was mutated in this run.`));if(c.data.blocked)return i(new Error(`Recreate pre-flight for ${t}: ${e.physicalId} (${e.logicalId}) has ${c.data.setting} enabled \u2014 CloudFormation cannot delete it, so the pinned-name replacement would fail mid-surgery. Disable it on the live resource (e.g. '${n.disableCommand(e.physicalId)}'), then re-run the deploy. Nothing was mutated in this run.`))}}return p(void 0)}u(D,"runRecreatePreFlight");export{D as runRecreatePreFlight};
|