1import * as DevOpsGuru from "@/AWS/DevOpsGuru";
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 DevOpsGuruTestFunction extends Lambda.Function<Lambda.Function>()(
14 "DevOpsGuruTestFunction",
15) {}
16
17export default DevOpsGuruTestFunction.make(
18 {
19 main,
20 functionUrl: true,
21 timeout: Duration.seconds(30),
22 },
23 Effect.gen(function* () {
24 // Event source: subscribe the host to insight lifecycle events. The
25 // deploy proves the EventBridge rule + invoke permission wiring.
26 yield* DevOpsGuru.consumeInsightEvents(
27 { kinds: ["new-insight", "severity-upgraded"] },
28 (events) =>
29 Stream.runForEach(events, (event) =>
30 Effect.log(
31 `devops-guru insight: ${event.detail.insightId} (${event.detail.insightSeverity})`,
32 ),
33 ),
34 );
35
36 // Every account-level capability the service exposes — the init phase
37 // registers the IAM grant for each; a representative subset is
38 // exercised over HTTP routes below.
39 const bound = {
40 describeAccountHealth: yield* DevOpsGuru.DescribeAccountHealth(),
41 describeAccountOverview: yield* DevOpsGuru.DescribeAccountOverview(),
42 describeAnomaly: yield* DevOpsGuru.DescribeAnomaly(),
43 describeFeedback: yield* DevOpsGuru.DescribeFeedback(),
44 describeInsight: yield* DevOpsGuru.DescribeInsight(),
45 describeOrganizationHealth:
46 yield* DevOpsGuru.DescribeOrganizationHealth(),
47 describeOrganizationOverview:
48 yield* DevOpsGuru.DescribeOrganizationOverview(),
49 describeOrganizationResourceCollectionHealth:
50 yield* DevOpsGuru.DescribeOrganizationResourceCollectionHealth(),
51 describeResourceCollectionHealth:
52 yield* DevOpsGuru.DescribeResourceCollectionHealth(),
53 getCostEstimation: yield* DevOpsGuru.GetCostEstimation(),
54 startCostEstimation: yield* DevOpsGuru.StartCostEstimation(),
55 listAnomaliesForInsight: yield* DevOpsGuru.ListAnomaliesForInsight(),
56 listAnomalousLogGroups: yield* DevOpsGuru.ListAnomalousLogGroups(),
57 listEvents: yield* DevOpsGuru.ListEvents(),
58 listInsights: yield* DevOpsGuru.ListInsights(),
59 listMonitoredResources: yield* DevOpsGuru.ListMonitoredResources(),
60 listOrganizationInsights: yield* DevOpsGuru.ListOrganizationInsights(),
61 listRecommendations: yield* DevOpsGuru.ListRecommendations(),
62 putFeedback: yield* DevOpsGuru.PutFeedback(),
63 searchInsights: yield* DevOpsGuru.SearchInsights(),
64 searchOrganizationInsights:
65 yield* DevOpsGuru.SearchOrganizationInsights(),
66 deleteInsight: yield* DevOpsGuru.DeleteInsight(),
67 };
68
69 return {
70 fetch: Effect.gen(function* () {
71 const request = yield* HttpServerRequest;
72 const url = new URL(request.originalUrl);
73 const pathname = url.pathname;
74
75 if (request.method === "GET" && pathname === "/bindings") {
76 return yield* HttpServerResponse.json({
77 bound: Object.keys(bound),
78 });
79 }
80
81 // Account health counters — works even before DevOps Guru is
82 // configured (returns zeroes).
83 if (request.method === "GET" && pathname === "/health") {
84 const health = yield* bound.describeAccountHealth();
85 return yield* HttpServerResponse.json({
86 openReactiveInsights: health.OpenReactiveInsights,
87 openProactiveInsights: health.OpenProactiveInsights,
88 metricsAnalyzed: health.MetricsAnalyzed,
89 });
90 }
91
92 // Account overview for the trailing 24 hours.
93 if (request.method === "GET" && pathname === "/overview") {
94 const overview = yield* bound.describeAccountOverview({
95 FromTime: new Date(Date.now() - 24 * 3600_000),
96 });
97 return yield* HttpServerResponse.json({
98 reactiveInsights: overview.ReactiveInsights,
99 proactiveInsights: overview.ProactiveInsights,
100 });
101 }
102
103 // Ongoing reactive insights.
104 if (request.method === "GET" && pathname === "/insights") {
105 const { ReactiveInsights } = yield* bound.listInsights({
106 StatusFilter: { Ongoing: { Type: "REACTIVE" } },
107 });
108 return yield* HttpServerResponse.json({
109 count: (ReactiveInsights ?? []).length,
110 });
111 }
112
113 // Search reactive insights in the trailing 24 hours. The service
114 // requires ToTime even though the SDK marks it optional.
115 if (request.method === "GET" && pathname === "/search") {
116 const { ReactiveInsights } = yield* bound.searchInsights({
117 Type: "REACTIVE",
118 StartTimeRange: {
119 FromTime: new Date(Date.now() - 24 * 3600_000),
120 ToTime: new Date(),
121 },
122 });
123 return yield* HttpServerResponse.json({
124 count: (ReactiveInsights ?? []).length,
125 });
126 }
127
128 // Per-stack health of the analyzed CloudFormation coverage.
129 if (request.method === "GET" && pathname === "/collection-health") {
130 const page = yield* bound.describeResourceCollectionHealth({
131 ResourceCollectionType: "AWS_CLOUD_FORMATION",
132 });
133 return yield* HttpServerResponse.json({
134 stacks: (page.CloudFormation ?? []).length,
135 });
136 }
137
138 // Monitored resources — a typed not-found means DevOps Guru has no
139 // resource collection configured; report zero coverage.
140 if (request.method === "GET" && pathname === "/monitored") {
141 const count = yield* bound.listMonitoredResources().pipe(
142 Effect.map(
143 ({ MonitoredResourceIdentifiers }) =>
144 MonitoredResourceIdentifiers.length,
145 ),
146 Effect.catchTag("ResourceNotFoundException", () =>
147 Effect.succeed(0),
148 ),
149 );
150 return yield* HttpServerResponse.json({ count });
151 }
152
153 return yield* HttpServerResponse.json(
154 { error: "Not found", method: request.method, pathname },
155 { status: 404 },
156 );
157 }).pipe(Effect.orDie),
158 };
159 }).pipe(
160 Effect.provide(
161 Layer.mergeAll(
162 Lambda.EventSource,
163 DevOpsGuru.DescribeAccountHealthHttp,
164 DevOpsGuru.DescribeAccountOverviewHttp,
165 DevOpsGuru.DescribeAnomalyHttp,
166 DevOpsGuru.DescribeFeedbackHttp,
167 DevOpsGuru.DescribeInsightHttp,
168 DevOpsGuru.DescribeOrganizationHealthHttp,
169 DevOpsGuru.DescribeOrganizationOverviewHttp,
170 DevOpsGuru.DescribeOrganizationResourceCollectionHealthHttp,
171 DevOpsGuru.DescribeResourceCollectionHealthHttp,
172 DevOpsGuru.GetCostEstimationHttp,
173 DevOpsGuru.StartCostEstimationHttp,
174 DevOpsGuru.ListAnomaliesForInsightHttp,
175 DevOpsGuru.ListAnomalousLogGroupsHttp,
176 DevOpsGuru.ListEventsHttp,
177 DevOpsGuru.ListInsightsHttp,
178 DevOpsGuru.ListMonitoredResourcesHttp,
179 DevOpsGuru.ListOrganizationInsightsHttp,
180 DevOpsGuru.ListRecommendationsHttp,
181 DevOpsGuru.PutFeedbackHttp,
182 DevOpsGuru.SearchInsightsHttp,
183 DevOpsGuru.SearchOrganizationInsightsHttp,
184 DevOpsGuru.DeleteInsightHttp,
185 ),
186 ),
187 ),
188);