1import type * as Effect from "effect/Effect";
2import type * as Stream from "effect/Stream";
3import {
4 consumeBusEvents,
5 type EventRecord,
6} from "../EventBridge/EventSource.ts";
7import type { AutoScalingGroup } from "./AutoScalingGroup.ts";
8
9/**
10 * EventBridge `detail-type`s EC2 Auto Scaling emits when a scaling activity
11 * finishes launching or terminating an instance.
12 */
13export type InstanceEventType =
14 | "EC2 Instance Launch Successful"
15 | "EC2 Instance Launch Unsuccessful"
16 | "EC2 Instance Terminate Successful"
17 | "EC2 Instance Terminate Unsuccessful";
18
19/**
20 * The `detail` payload EC2 Auto Scaling delivers to EventBridge when a
21 * scaling activity launches or terminates an instance.
22 */
23export interface InstanceEventDetail {
24 /**
25 * ID of the scaling activity that produced the event.
26 */
27 ActivityId: string;
28 /**
29 * Name of the Auto Scaling Group the instance belongs to.
30 */
31 AutoScalingGroupName: string;
32 /**
33 * ID of the launched/terminated EC2 instance.
34 */
35 EC2InstanceId: string;
36 /**
37 * Final status of the activity (e.g. `InProgress`, `Successful`, `Failed`).
38 */
39 StatusCode: string;
40 /**
41 * Human-readable status message for unsuccessful activities.
42 */
43 StatusMessage?: string;
44 /**
45 * What initiated the activity (e.g. a policy execution or capacity change).
46 */
47 Cause?: string;
48 /**
49 * ISO-8601 time the activity started.
50 */
51 StartTime?: string;
52 /**
53 * ISO-8601 time the activity ended.
54 */
55 EndTime?: string;
56 /**
57 * Additional context, e.g. `{ "Availability Zone": "...", "Subnet ID": "..." }`.
58 */
59 Details?: Record<string, string>;
60 /**
61 * Where the instance is coming from (e.g. `EC2` or `WarmPool`).
62 */
63 Origin?: string;
64 /**
65 * Where the instance is headed (e.g. `AutoScalingGroup` or `WarmPool`).
66 */
67 Destination?: string;
68}
69
70/** An instance launch/terminate EventBridge event delivered to the handler. */
71export type InstanceEvent = EventRecord<InstanceEventDetail>;
72
73export interface InstanceEventSourceProps {
74 /**
75 * Logical id prefix for the EventBridge rule. Defaults to the Auto Scaling
76 * Group's logical id.
77 */
78 id?: string;
79 /**
80 * Which instance events to deliver.
81 * @default all four launch/terminate success/failure events
82 */
83 events?: InstanceEventType[];
84}
85
86const allInstanceEvents: InstanceEventType[] = [
87 "EC2 Instance Launch Successful",
88 "EC2 Instance Launch Unsuccessful",
89 "EC2 Instance Terminate Successful",
90 "EC2 Instance Terminate Unsuccessful",
91];
92
93/**
94 * Deliver EC2 Auto Scaling instance launch/terminate events to the host
95 * Function via EventBridge. Unlike {@link consumeLifecycleActions} this does
96 * not pause the instance transition — it observes completed scaling
97 * activities, e.g. to deregister an instance from an external system after
98 * termination or alert on launch failures.
99 *
100 * The EventBridge pattern matches every event of the chosen detail-types in
101 * the account; inspect `event.detail.AutoScalingGroupName` in the handler if
102 * multiple groups share the Function.
103 *
104 * ### Observing the Fleet
105 * **Example:** React to instance launches and terminations
106 * ```typescript
107 * yield* consumeInstanceEvents(
108 * group,
109 * {
110 * events: [
111 * "EC2 Instance Launch Successful",
112 * "EC2 Instance Terminate Successful",
113 * ],
114 * },
115 * (events) =>
116 * Stream.runForEach(events, (event) =>
117 * Effect.log(
118 * `${event["detail-type"]}: ${event.detail.EC2InstanceId} in ${event.detail.AutoScalingGroupName}`,
119 * ),
120 * ),
121 * );
122 * ```
123 *
124 * **Example:** Alert on launch failures
125 * ```typescript
126 * yield* consumeInstanceEvents(
127 * group,
128 * { events: ["EC2 Instance Launch Unsuccessful"] },
129 * (events) =>
130 * Stream.runForEach(events, (event) =>
131 * notify(`Launch failed: ${event.detail.StatusMessage}`),
132 * ),
133 * );
134 * ```
135 */
136export const consumeInstanceEvents = <StreamReq = never, Req = never>(
137 group: AutoScalingGroup,
138 props: InstanceEventSourceProps,
139 process: (
140 events: Stream.Stream<InstanceEvent, never, StreamReq>,
141 ) => Effect.Effect<void, never, Req>,
142) =>
143 // The pattern uses only literal `source` + `detail-type` so it round-trips
144 // through both the deploy-time rule and the runtime matcher (Output values
145 // cannot appear in the pattern — they don't resolve inside the deployed
146 // bundle).
147 consumeBusEvents(
148 `${props.id ?? group.LogicalId}-InstanceEvents`,
149 {
150 source: ["aws.autoscaling"],
151 "detail-type": props.events ?? allInstanceEvents,
152 },
153 process,
154 );