| 1 | import * as logs from "@distilled.cloud/aws/cloudwatch-logs"; |
| 2 | import * as Effect from "effect/Effect"; |
| 3 | import * 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 | */ |
| 12 | export 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. */ |
| 18 | export 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 | */ |
| 35 | export 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). */ |
| 45 | export const deleteLogGroups = (logGroupNames: readonly string[]) => |
| 46 | Effect.forEach(logGroupNames, (logGroupName) => |
| 47 | logs |
| 48 | .deleteLogGroup({ logGroupName }) |
| 49 | .pipe(Effect.catchTag("ResourceNotFoundException", () => Effect.void)), |
| 50 | ); |