1import * as logs from "@distilled.cloud/aws/cloudwatch-logs";
2import * as Effect from "effect/Effect";
3import * as Schedule from "effect/Schedule";
4
5/**
6 * The two CloudWatch log groups App Runner auto-creates for a service.
7 *
8 * Deliberately spelled out here rather than imported from the provider:
9 * a wrong format in the provider would make its reap miss the real groups,
10 * and only an independently-derived name catches that.
11 */
12export const logGroupNamesFor = (serviceName: string, serviceId: string) => [
13 `/aws/apprunner/${serviceName}/${serviceId}/application`,
14 `/aws/apprunner/${serviceName}/${serviceId}/service`,
15];
16
17/** Which of the given log groups currently exist, in order. */
18export const observeLogGroups = (logGroupNames: readonly string[]) =>
19 Effect.forEach(logGroupNames, (logGroupName) =>
20 logs
21 .describeLogGroups({ logGroupNamePrefix: logGroupName, limit: 1 })
22 .pipe(
23 Effect.map((response) =>
24 (response.logGroups ?? []).some(
25 (group) => group.logGroupName === logGroupName,
26 ),
27 ),
28 ),
29 );
30
31/**
32 * Wait until every group exists. App Runner creates them around the first
33 * deployment, which can lag the service reaching RUNNING by a few seconds.
34 */
35export const awaitLogGroups = (logGroupNames: readonly string[]) =>
36 observeLogGroups(logGroupNames).pipe(
37 Effect.repeat({
38 schedule: Schedule.spaced("5 seconds"),
39 until: (present) => present.every(Boolean),
40 times: 24,
41 }),
42 );
43
44/** Delete the groups out-of-band (cleanup for retained-log-group coverage). */
45export const deleteLogGroups = (logGroupNames: readonly string[]) =>
46 Effect.forEach(logGroupNames, (logGroupName) =>
47 logs
48 .deleteLogGroup({ logGroupName })
49 .pipe(Effect.catchTag("ResourceNotFoundException", () => Effect.void)),
50 );