1import * as acm from "@distilled.cloud/aws/acm";
2import * as Data from "effect/Data";
3import * as Effect from "effect/Effect";
4import * as Schedule from "effect/Schedule";
5import * as Stream from "effect/Stream";
6
7class CertificateImportFailed extends Data.TaggedError(
8 "CertificateImportFailed",
9)<{ readonly domainName: string }> {}
10
11/**
12 * Find an existing imported test certificate by domain (a previous run may
13 * have left one behind) or import the checked-in self-signed fixture. Import
14 * without a CertificateArn mints a new certificate each call, so reuse keeps
15 * repeated runs deterministic and leak-free.
16 */
17export const ensureImportedCert = Effect.fn(function* (
18 domainName: string,
19 certPem: string,
20 keyPem: string,
21) {
22 const pages = yield* acm.listCertificates
23 .pages({ CertificateStatuses: ["ISSUED"] })
24 .pipe(
25 Stream.runCollect,
26 Effect.map((chunk) =>
27 Array.from(chunk).flatMap((page) => page.CertificateSummaryList ?? []),
28 ),
29 );
30 const existing = pages.find((c) => c.DomainName === domainName);
31 if (existing?.CertificateArn) {
32 return existing.CertificateArn;
33 }
34 const encode = (pem: string) =>
35 Effect.sync(() => new TextEncoder().encode(pem));
36 const imported = yield* acm.importCertificate({
37 Certificate: yield* encode(certPem),
38 PrivateKey: yield* encode(keyPem),
39 });
40 if (!imported.CertificateArn) {
41 return yield* Effect.fail(new CertificateImportFailed({ domainName }));
42 }
43 return imported.CertificateArn;
44});
45
46/**
47 * Deleting right after the listener detaches can race eventual consistency;
48 * retry briefly, then leave the certificate behind (the next run's
49 * `ensureImportedCert` reclaims it by domain). Not-found is success
50 * (idempotent), still-in-use is the documented leave-for-reuse path, and any
51 * other error is a defect — so the error channel is `never` and this is a
52 * valid `Effect.ensuring` finalizer.
53 */
54export const deleteCertBestEffort = Effect.fn(function* (
55 certificateArn: string,
56) {
57 yield* acm.deleteCertificate({ CertificateArn: certificateArn }).pipe(
58 Effect.retry({
59 while: (e) => e._tag === "ResourceInUseException",
60 schedule: Schedule.max([
61 Schedule.spaced("3 seconds"),
62 Schedule.recurs(10),
63 ]),
64 }),
65 Effect.catchTag("ResourceInUseException", () =>
66 Effect.logWarning(
67 `certificate ${certificateArn} still in use; leaving it for reuse`,
68 ),
69 ),
70 Effect.catchTag("ResourceNotFoundException", () => Effect.void),
71 Effect.orDie,
72 );
73});