| 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 commonly-consumed AWS Organizations event names. Organizations |
| 11 | * publishes CloudTrail-backed events — `CreateAccountResult` arrives as an |
| 12 | * AWS service event when an asynchronous `CreateAccount` request completes; |
| 13 | * the management operations (account moves, OU changes, policy attachments, |
| 14 | * handshakes) arrive as AWS API call events. |
| 15 | */ |
| 16 | export type OrganizationsEventName = |
| 17 | | "CreateAccountResult" |
| 18 | | "CreateOrganizationalUnit" |
| 19 | | "DeleteOrganizationalUnit" |
| 20 | | "MoveAccount" |
| 21 | | "RemoveAccountFromOrganization" |
| 22 | | "CloseAccount" |
| 23 | | "InviteAccountToOrganization" |
| 24 | | "AcceptHandshake" |
| 25 | | "DeclineHandshake" |
| 26 | | "CancelHandshake" |
| 27 | | "AttachPolicy" |
| 28 | | "DetachPolicy" |
| 29 | | "TagResource" |
| 30 | | "UntagResource"; |
| 31 | |
| 32 | /** |
| 33 | * The `detail` payload AWS Organizations delivers to EventBridge. The events |
| 34 | * are published as CloudTrail records (service events for asynchronous |
| 35 | * outcomes like `CreateAccountResult`, API call events for management |
| 36 | * operations), so the detail carries the CloudTrail record shape; the |
| 37 | * asynchronous outcome lives under `serviceEventDetails`. |
| 38 | */ |
| 39 | export interface OrganizationsEventDetail { |
| 40 | /** The event name, e.g. `CreateAccountResult` or `MoveAccount`. */ |
| 41 | eventName?: OrganizationsEventName | (string & {}); |
| 42 | /** Always `organizations.amazonaws.com`. */ |
| 43 | eventSource?: string; |
| 44 | /** Always `us-east-1` — Organizations is a global service homed there. */ |
| 45 | awsRegion?: string; |
| 46 | /** When the operation completed. */ |
| 47 | eventTime?: string; |
| 48 | /** For API call events, the request parameters of the operation. */ |
| 49 | requestParameters?: Record<string, unknown> | null; |
| 50 | /** For API call events, the response elements of the operation. */ |
| 51 | responseElements?: Record<string, unknown> | null; |
| 52 | /** |
| 53 | * For service events (e.g. `CreateAccountResult`), the asynchronous |
| 54 | * outcome — `createAccountStatus.state` is `SUCCEEDED` or `FAILED`. |
| 55 | */ |
| 56 | serviceEventDetails?: Record<string, unknown>; |
| 57 | /** Additional CloudTrail record fields (the schema grows over time). */ |
| 58 | [key: string]: unknown; |
| 59 | } |
| 60 | |
| 61 | /** An AWS Organizations EventBridge event delivered to the handler. */ |
| 62 | export type OrganizationsEvent = EventRecord<OrganizationsEventDetail>; |
| 63 | |
| 64 | export interface OrganizationsEventSourceProps extends EventRouteProps { |
| 65 | /** |
| 66 | * Logical id for the backing EventBridge rule. |
| 67 | * @default "OrganizationsEvents" |
| 68 | */ |
| 69 | id?: string; |
| 70 | /** |
| 71 | * Which event names to subscribe to (matched against `detail.eventName`), |
| 72 | * e.g. `["CreateAccountResult"]`. |
| 73 | * @default all Organizations events |
| 74 | */ |
| 75 | events?: readonly (OrganizationsEventName | (string & {}))[]; |
| 76 | } |
| 77 | |
| 78 | /** |
| 79 | * Event source connecting AWS Organizations events to the hosting compute. |
| 80 | * Organizations publishes CloudTrail-backed events — asynchronous |
| 81 | * account-creation outcomes (`CreateAccountResult`) and every management |
| 82 | * operation (account moves, OU changes, policy attachments, handshake |
| 83 | * responses) — to the **management account's** default EventBridge bus in |
| 84 | * **us-east-1** (source `aws.organizations`); this subscribes the host |
| 85 | * Function to those events so it can drive account-vending, baseline, or |
| 86 | * compliance automation. |
| 87 | * |
| 88 | * Organizations publishes to EventBridge automatically — no additional |
| 89 | * resource is created besides the EventBridge rule targeting the host. The |
| 90 | * rule must be deployed in us-east-1 of the management account to receive |
| 91 | * events. Provide the host-specific implementation layer (e.g. |
| 92 | * `AWS.Lambda.EventSource`) on the Function effect. |
| 93 | * |
| 94 | * ### Consuming Organization Events |
| 95 | * **Example:** React to Completed Account Creations |
| 96 | * ```typescript |
| 97 | * import * as AWS from "alchemy/AWS"; |
| 98 | * |
| 99 | * export default VendingFunction.make( |
| 100 | * { main: import.meta.url }, |
| 101 | * Effect.gen(function* () { |
| 102 | * yield* AWS.Organizations.consumeOrganizationsEvents( |
| 103 | * { events: ["CreateAccountResult"] }, |
| 104 | * (events) => |
| 105 | * Stream.runForEach(events, (event) => |
| 106 | * Effect.log( |
| 107 | * `account creation: ${JSON.stringify( |
| 108 | * event.detail.serviceEventDetails, |
| 109 | * )}`, |
| 110 | * ), |
| 111 | * ), |
| 112 | * ); |
| 113 | * return {}; |
| 114 | * }).pipe(Effect.provide(AWS.Lambda.EventSource)), |
| 115 | * ); |
| 116 | * ``` |
| 117 | */ |
| 118 | export const consumeOrganizationsEvents = <StreamReq = never, Req = never>( |
| 119 | props: OrganizationsEventSourceProps, |
| 120 | process: ( |
| 121 | events: Stream.Stream<OrganizationsEvent, never, StreamReq>, |
| 122 | ) => Effect.Effect<void, never, Req>, |
| 123 | ) => |
| 124 | consumeBusEvents( |
| 125 | props.id ?? "OrganizationsEvents", |
| 126 | { |
| 127 | source: ["aws.organizations"], |
| 128 | ...(props.events !== undefined |
| 129 | ? { detail: { eventName: [...props.events] } } |
| 130 | : {}), |
| 131 | }, |
| 132 | { description: props.description, state: props.state }, |
| 133 | process, |
| 134 | ); |