| 1 | import type * as Effect from "effect/Effect"; |
| 2 | import type * as Stream from "effect/Stream"; |
| 3 | import { |
| 4 | consumeBusEvents, |
| 5 | type EventRecord, |
| 6 | type EventRouteProps, |
| 7 | } from "../EventBridge/EventSource.ts"; |
| 8 | |
| 9 | /** |
| 10 | * The `detail` payload IoT Greengrass V2 delivers to EventBridge. |
| 11 | * Deployment-status events describe a deployment landing on a core device |
| 12 | * (`deploymentId`, `coreDeviceExecutionStatus`); component-status events |
| 13 | * describe an installed component's lifecycle state (`componentName`, |
| 14 | * `componentLifecycleState`, e.g. `BROKEN`). Fields not shared by every |
| 15 | * event kind are optional (the schema grows over time). |
| 16 | */ |
| 17 | export interface GreengrassEventDetail { |
| 18 | /** The core device thing name the event is about. */ |
| 19 | coreDeviceThingName?: string; |
| 20 | /** Deployment-status events: the id of the deployment. */ |
| 21 | deploymentId?: string; |
| 22 | /** |
| 23 | * Deployment-status events: the per-device execution status, e.g. |
| 24 | * `SUCCEEDED`, `FAILED`, `REJECTED`, `TIMED_OUT`. |
| 25 | */ |
| 26 | coreDeviceExecutionStatus?: string; |
| 27 | /** Component-status events: the name of the installed component. */ |
| 28 | componentName?: string; |
| 29 | /** Component-status events: the version of the installed component. */ |
| 30 | componentVersion?: string; |
| 31 | /** |
| 32 | * Component-status events: the component's lifecycle state, e.g. |
| 33 | * `RUNNING`, `ERRORED`, `BROKEN`. |
| 34 | */ |
| 35 | componentLifecycleState?: string; |
| 36 | /** Additional event fields (the schema grows over time). */ |
| 37 | [key: string]: unknown; |
| 38 | } |
| 39 | |
| 40 | /** An IoT Greengrass V2 EventBridge event delivered to the handler. */ |
| 41 | export type GreengrassEvent = EventRecord<GreengrassEventDetail>; |
| 42 | |
| 43 | /** Which IoT Greengrass V2 notifications to subscribe to. */ |
| 44 | export type GreengrassEventKind = "deployment-status" | "component-status"; |
| 45 | |
| 46 | const DETAIL_TYPES: Record<GreengrassEventKind, string> = { |
| 47 | "deployment-status": "Greengrass V2 Effective Deployment Status Change", |
| 48 | "component-status": "Greengrass V2 Installed Component Status Change", |
| 49 | }; |
| 50 | |
| 51 | export interface GreengrassEventSourceProps extends EventRouteProps { |
| 52 | /** |
| 53 | * Logical id for the backing EventBridge rule. |
| 54 | * @default "GreengrassEvents" |
| 55 | */ |
| 56 | id?: string; |
| 57 | /** |
| 58 | * Which notifications to subscribe to: per-device deployment status |
| 59 | * changes (a deployment succeeding/failing on a core device) and/or |
| 60 | * installed component status changes (a component entering `BROKEN`). |
| 61 | * @default ["deployment-status"] |
| 62 | */ |
| 63 | kinds?: readonly GreengrassEventKind[]; |
| 64 | } |
| 65 | |
| 66 | /** |
| 67 | * Event source connecting IoT Greengrass V2 notifications to the hosting |
| 68 | * compute. Greengrass publishes per-core-device deployment execution status |
| 69 | * (`SUCCEEDED`, `FAILED`, …) and installed-component lifecycle changes (most |
| 70 | * importantly a component dropping into `BROKEN`) to the account's default |
| 71 | * EventBridge bus (source `aws.greengrass`); this subscribes the host |
| 72 | * Function to those events so it can alert on failed rollouts or broken |
| 73 | * edge software. |
| 74 | * |
| 75 | * Greengrass publishes to EventBridge automatically — no additional resource |
| 76 | * is created besides the EventBridge rule targeting the host. Provide the |
| 77 | * host-specific implementation layer (e.g. `AWS.Lambda.EventSource`) on the |
| 78 | * Function effect. |
| 79 | * |
| 80 | * ### Consuming Greengrass Events |
| 81 | * **Example:** Alert When A Rollout Fails On A Device |
| 82 | * ```typescript |
| 83 | * import * as AWS from "alchemy/AWS"; |
| 84 | * |
| 85 | * export default AlertFunction.make( |
| 86 | * { main: import.meta.url }, |
| 87 | * Effect.gen(function* () { |
| 88 | * yield* AWS.GreengrassV2.consumeGreengrassEvents( |
| 89 | * { kinds: ["deployment-status"] }, |
| 90 | * (events) => |
| 91 | * Stream.runForEach(events, (event) => |
| 92 | * event.detail.coreDeviceExecutionStatus === "FAILED" |
| 93 | * ? Effect.logError( |
| 94 | * `deployment ${event.detail.deploymentId} failed on ${event.detail.coreDeviceThingName}`, |
| 95 | * ) |
| 96 | * : Effect.void, |
| 97 | * ), |
| 98 | * ); |
| 99 | * return {}; |
| 100 | * }).pipe(Effect.provide(AWS.Lambda.EventSource)), |
| 101 | * ); |
| 102 | * ``` |
| 103 | */ |
| 104 | export const consumeGreengrassEvents = <StreamReq = never, Req = never>( |
| 105 | props: GreengrassEventSourceProps, |
| 106 | process: ( |
| 107 | events: Stream.Stream<GreengrassEvent, never, StreamReq>, |
| 108 | ) => Effect.Effect<void, never, Req>, |
| 109 | ) => |
| 110 | consumeBusEvents( |
| 111 | props.id ?? "GreengrassEvents", |
| 112 | { |
| 113 | source: ["aws.greengrass"], |
| 114 | "detail-type": (props.kinds ?? (["deployment-status"] as const)).map( |
| 115 | (kind) => DETAIL_TYPES[kind], |
| 116 | ), |
| 117 | }, |
| 118 | { description: props.description, state: props.state }, |
| 119 | process, |
| 120 | ); |