import { CloudFormationClient } from "@aws-sdk/client-cloudformation"; import type { AwsProvider } from "../../aws/AwsProvider.js"; import { type CloudFormationStackStatus, type CloudFormationOutput } from "../../aws/utils/stackStatus.js"; import { type ProgressCallbacks } from "../../types/ProgressEvent.js"; import { type Result } from "@fjall/generator"; import { CloudFormationError } from "./cloudFormationError.js"; import { type StackUpdateWatch } from "./stackUpdateFollow.js"; export { CloudFormationError }; /** * One live resource row from ListStackResources. `physicalId` is absent for * rows CloudFormation reports without one (resources mid-provisioning). * `resourceStatus` is the row's `ResourceStatus` verbatim (`CREATE_COMPLETE`, * `DELETE_SKIPPED`, …) — a listing keeps rows the stack no longer manages, * such as a Retain resource skipped by a rollback, so a consumer deciding * ownership reads the status, never presence alone. Re-exported by the * rename-detection overlay as its `StackPhysicalResource` input type — * coupled shapes, single declaration. */ export interface StackResourceSummaryRow { logicalId: string; physicalId?: string; resourceType: string; resourceStatus?: string; } /** * Callbacks for CloudFormation operations */ export interface CloudFormationCallbacks extends ProgressCallbacks { onStackCheck?: (stackName: string) => void; onStackFound?: (stackName: string, status: string) => void; onStackNotFound?: (stackName: string) => void; onOutputsRetrieved?: (stackName: string, outputCount: number) => void; } /** * What DescribeStacks said about one stack's outputs. `exists: false` is * positive absence — CloudFormation's own not-found refusal — never an * inference from an empty response; a deployed stack that declares no * outputs is `{ exists: true, outputs: [] }`. */ export type StackOutputsDescription = { exists: false; } | { exists: true; outputs: CloudFormationOutput[]; }; /** * The one read the `EcsServiceResolver` needs: a stack's outputs, with * absence and failure kept apart. Typing the resolver against this seam * rather than the service lets a consumer that already holds a * CloudFormation client (the webapp worker, the CLI) supply its own reader * without an `as never` cast across the package boundary. */ export interface StackOutputsReader { /** * Describe the stack's outputs. Resolves `{ exists: false }` only when * CloudFormation reports the stack does not exist; a stack with no outputs * resolves `{ exists: true, outputs: [] }`; every other error is a failure * — a consumer must never read a failed read as an empty stack. */ describeStackOutputs(stackName: string, abortSignal?: AbortSignal): Promise>; } /** * CloudFormation service using the AwsProvider interface. * * Unlike the CLI version which lazily initialises AwsContext, * deploy-core receives a pre-configured AwsProvider at construction. */ export declare class CloudFormationService implements StackOutputsReader { private aws; private readonly regionPinnedClients; constructor(aws: AwsProvider); /** * The client a call runs through: the provider's own when `region` is * absent or is the provider's region, else one pinned to `region` and * memoised for the service's lifetime. The `UsEast1*` application stacks * are the callers — a probe of one from the target region answers about a * stack that does not exist there. * * A pinned client captures the provider's credentials at first use and * keeps them for the service's lifetime; no deploy-core provider refreshes * credentials mid-run, so a pinned client ages exactly as the provider's * own does. */ private clientFor; /** * Classify an AWS SDK error into a typed CloudFormationError. */ private classifyAwsError; describeStackOutputs(stackName: string, abortSignal?: AbortSignal): Promise>; /** * Stack outputs flattened for callers that treat an absent stack as one * with no outputs: `describeStackOutputs` with absence collapsed to `[]` * (reported through `onStackNotFound`). Callers that must tell the two * apart read `describeStackOutputs` directly. */ getStackOutputs(stackName: string, callbacks?: CloudFormationCallbacks, abortSignal?: AbortSignal): Promise>; /** * Get stack status directly. The send always composes the caller's abort * signal (when present) with the SDK timeout via composeSdkAbortSignal; * polling callers survive a timeout blip because classifyAwsError maps * TimeoutError to a recoverable "timeout" error. `region` pins the probe * to a stack's own region (`stackRegionFor`); absent, the provider's. */ getStackStatus(stackName: string, callbacks?: CloudFormationCallbacks, abortSignal?: AbortSignal, region?: string): Promise>; /** * Look up CloudFormation exports by exact name, paging until every name is * found. A name absent from a COMPLETED walk is proof the account exports * no such name; an abort mid-walk is not, so it fails and says which names * it never settled rather than handing back a map that reads as proof. */ getExportsByNames(names: string[], abortSignal?: AbortSignal): Promise, CloudFormationError>>; /** * Delete a CloudFormation stack directly via the API. * Unlike CDK destroy, this works on stacks in DELETE_FAILED state. * `region` pins the delete to the stack's own region; absent, the provider's. */ deleteStack(stackName: string, abortSignal?: AbortSignal, region?: string): Promise>; /** * Check whether a CloudFormation stack exists as a deployable, healthy stack. * * Returns false for stacks that never reached a successful deployment * (REVIEW_IN_PROGRESS and the SAFE_CLEANUP_STATES: ROLLBACK_FAILED / * ROLLBACK_COMPLETE / DELETE_FAILED). Both callers are deploy skip-gates -- * treating a broken stack as "exists" would skip the deploy that is needed * to replace it. `region` pins the probe to the stack's own region when no * `client` is given; absent, the provider's. */ stackExists(stackName: string, client?: CloudFormationClient, abortSignal?: AbortSignal, region?: string): Promise; /** * Fetch the deployed CloudFormation template for a stack, used by the deploy * approval gate to diff the live template against the freshly synthesised one. * * Uses TemplateStage "Original" — the user-submitted template, which matches * the raw cdk.out form (CDK resolves everything at synth, so Original and * Processed coincide for CDK apps). A stack that has never deployed returns a * stack_not_found failure; the gate treats that as an empty baseline (every * resource is a creation). `region` pins the read to the stack's own * region; absent, the provider's. */ getTemplate(stackName: string, abortSignal?: AbortSignal, region?: string): Promise>; /** * List the stack's live resources (logical ID → physical ID → type), used by * the rename-detection overlay to anchor delete rows to the physical * resources they would orphan or destroy. Paginated; a never-deployed stack * returns a stack_not_found failure, which the caller treats as "no live * resources" (nothing to rename). `region` pins the listing to the stack's * own region; absent, the provider's. */ listStackResources(stackName: string, abortSignal?: AbortSignal, region?: string): Promise>; /** * Poll stack status until deletion completes, fails, or times out. * `region` pins the polls to the stack's own region; absent, the provider's. */ waitForDeleteComplete(stackName: string, options?: { timeoutMs?: number; pollIntervalMs?: number; onProgress?: (message: string) => void; abortSignal?: AbortSignal; region?: string; }): Promise>; /** * Converge a subset of a stack's parameters to new values, preserving every * other parameter via `UsePreviousValue` (F4b — post-rollback CFN ImageTag * reconcile). Keys in `parameterOverrides` absent from the stack's current * parameters are reported as `skippedKeys` rather than failing the call — * a stale key (a service the stack no longer has) should not block * reconciling the keys that do exist. * * With `watch`, the update is followed as a deploy follows a stack * (`followStackUpdate`, FJL-995): its resource events reach the caller's * ECS tail, and the engine ends an update ECS rolled back that * CloudFormation stopped driving. Without it, the wait reads only the * stack's status, for ten minutes, and such an update is left running when * the wait gives up. * * The update is sent under a request token of its own, one per call. * CloudFormation's API reference documents the token as what tells a * resend of a request it took from a new one, and gives it to every event * of the update. Only a refusal CloudFormation answered to the one request * the SDK sent (`isAnsweredRefusal`) says the update did not start. Any * other failure is settled by the stack's newest events * (`unansweredUpdateVerdict`): the update is followed, or waited on, when * the newest stack-level event carries its token; the failure stands when * another operation's does; and when the events cannot say, the failure * says the update may have started. A caller that cancelled before the * send sends nothing. */ updateStackParameters(stackName: string, parameterOverrides: Record, abortSignal?: AbortSignal, watch?: StackUpdateWatch): Promise>; /** * Sends the parameter update under `clientRequestToken` and, when no * answer says whether CloudFormation took it, reads whether it started: * see `updateStackParameters`. `floor` is the stack's newest event before * the send, when one was read; `seen`, when the update is followed, waits * on the follow's own stream of the stack's events for the update's start. */ private startParameterUpdate; /** * Whether a parameter update no answer settled started. A followed update * is settled first from the follow's own stream of the stack's events: * started once it shows an event carrying the update's token, within * `UNANSWERED_UPDATE_SETTLE_MS`. The monitor behind it pages back to the * events it has read and retries a failed read on its next poll, so * neither an update that logged more than a page of events nor one read * that failed hides the start, nor does a first event listed a few seconds * late. What the stream has not shown by then, or an update with no follow, * is settled by the stack's newest events (`unansweredUpdateVerdict`), and * the stream is asked once more after that read, so an update whose start * reached the stream meanwhile is still followed. A caller that cancelled * meanwhile is not kept waiting. */ private settleUnansweredUpdate; /** Sends the parameter-only update once, under the call's request token. */ private sendParameterUpdate; /** * Sends the update once a monitor of the stack is primed and follows it, * beside the caller's ECS tail and the engine's hung-update watch. */ private followParameterUpdate; /** * The newest event the stack holds before an update this service sends: * every event at or before it belongs to an earlier operation. Read with * `fetchNewestStackEvents`, the read that settles an update whose answer * was lost. When it cannot be read, the local time now, so no earlier * operation's event is read as the update's end; on a host whose clock runs * ahead the update's first events may then go unseen. */ private readEventFloor; /** * Poll stack status until the parameter-only update completes, fails, or * times out. */ private waitForUpdateStackParametersComplete; }