1import * as DLM from "@/AWS/DLM";
2import * as Lambda from "@/AWS/Lambda";
3import * as Duration from "effect/Duration";
4import * as Effect from "effect/Effect";
5import * as Layer from "effect/Layer";
6import * as Stream from "effect/Stream";
7import { HttpServerRequest } from "effect/http/HttpServerRequest";
8import * as HttpServerResponse from "effect/http/HttpServerResponse";
9import path from "pathe";
10
11const main = path.resolve(import.meta.dirname, "handler.ts");
12
13export class DlmTestFunction extends Lambda.Function<Lambda.Function>()(
14 "DlmTestFunction",
15) {}
16
17export default DlmTestFunction.make(
18 {
19 main,
20 functionUrl: true,
21 timeout: Duration.seconds(30),
22 },
23 Effect.gen(function* () {
24 // The lifecycle policy the policy-scoped binding is bound to. DISABLED
25 // and targeting a tag no volume carries, so it never creates snapshots.
26 const policy = yield* DLM.LifecyclePolicy("BindingPolicy", {
27 description: "alchemy dlm bindings fixture policy",
28 state: "DISABLED",
29 policyDetails: {
30 resourceTypes: ["VOLUME"],
31 targetTags: { AlchemyDlmBindings: "true" },
32 schedules: [
33 {
34 name: "Daily",
35 createRule: { interval: 24, intervalUnit: "HOURS" },
36 retainRule: { count: 1 },
37 },
38 ],
39 },
40 });
41
42 // Event source: subscribe the host to DLM policy state-change events.
43 // The deploy proves the EventBridge rule + invoke permission wiring.
44 yield* DLM.consumePolicyEvents({ kinds: ["state-change"] }, (events) =>
45 Stream.runForEach(events, (event) =>
46 Effect.log(
47 `dlm state change: ${event.detail.policy_id} -> ${event.detail.state}`,
48 ),
49 ),
50 );
51
52 const getLifecyclePolicy = yield* DLM.GetLifecyclePolicy(policy);
53 const getLifecyclePolicies = yield* DLM.GetLifecyclePolicies();
54
55 const bound = { getLifecyclePolicy, getLifecyclePolicies };
56
57 return {
58 fetch: Effect.gen(function* () {
59 const request = yield* HttpServerRequest;
60 const url = new URL(request.originalUrl);
61 const pathname = url.pathname;
62
63 if (request.method === "GET" && pathname === "/bindings") {
64 return yield* HttpServerResponse.json({
65 bound: Object.keys(bound),
66 });
67 }
68
69 // Policy-scoped read: the PolicyId is injected from the binding.
70 if (request.method === "GET" && pathname === "/policy") {
71 const { Policy } = yield* getLifecyclePolicy();
72 return yield* HttpServerResponse.json({
73 policyId: Policy?.PolicyId,
74 state: Policy?.State,
75 policyType: Policy?.PolicyDetails?.PolicyType,
76 });
77 }
78
79 // Account-level list, filtered down to the fixture's policy state.
80 if (request.method === "GET" && pathname === "/policies") {
81 const { Policies } = yield* getLifecyclePolicies({
82 State: "DISABLED",
83 });
84 return yield* HttpServerResponse.json({
85 ids: (Policies ?? []).map((summary) => summary.PolicyId),
86 });
87 }
88
89 return yield* HttpServerResponse.json(
90 { error: "Not found", method: request.method, pathname },
91 { status: 404 },
92 );
93 }).pipe(Effect.orDie),
94 };
95 }).pipe(
96 Effect.provide(
97 Layer.mergeAll(
98 Lambda.EventSource,
99 DLM.GetLifecyclePolicyHttp,
100 DLM.GetLifecyclePoliciesHttp,
101 ),
102 ),
103 ),
104);