1import * as AWS from "@/AWS";
2import { PrefixList } from "@/AWS/EC2";
3import * as Provider from "@/Provider";
4import * as Test from "@/Test/Alchemy";
5import * as EC2 from "@distilled.cloud/aws/ec2";
6import { expect } from "alchemy-test";
7import * as Data from "effect/Data";
8import * as Effect from "effect/Effect";
9import { MinimumLogLevel } from "effect/References";
10import * as Schedule from "effect/Schedule";
11
12const { test } = Test.make({ providers: AWS.providers() });
13
14const logLevel = Effect.provideService(
15 MinimumLogLevel,
16 process.env.DEBUG ? "Debug" : "Info",
17);
18
19class PrefixListStillExists extends Data.TaggedError("PrefixListStillExists") {}
20
21const getEntries = (prefixListId: string) =>
22 EC2.getManagedPrefixListEntries({ PrefixListId: prefixListId }).pipe(
23 Effect.map((r) =>
24 Object.fromEntries(
25 (r.Entries ?? []).map((e) => [e.Cidr!, e.Description ?? ""]),
26 ),
27 ),
28 );
29
30const assertDeleted = Effect.fn(function* (prefixListId: string) {
31 yield* EC2.describeManagedPrefixLists({ PrefixListIds: [prefixListId] }).pipe(
32 Effect.flatMap((r) => {
33 const state = r.PrefixLists?.[0]?.State;
34 return state === undefined || state === "delete-complete"
35 ? Effect.void
36 : Effect.fail(new PrefixListStillExists());
37 }),
38 Effect.retry({
39 while: (e) => e instanceof PrefixListStillExists,
40 schedule: Schedule.max([Schedule.exponential(200), Schedule.recurs(8)]),
41 }),
42 Effect.catchTag("InvalidPrefixListID.NotFound", () => Effect.void),
43 );
44});
45
46test.provider(
47 "create, update entries, delete managed prefix list",
48 (stack) =>
49 Effect.gen(function* () {
50 yield* stack.destroy();
51
52 // Create with two entries.
53 const { prefixList } = yield* stack.deploy(
54 Effect.gen(function* () {
55 const prefixList = yield* PrefixList("TestPrefixList", {
56 maxEntries: 5,
57 entries: [
58 { cidr: "10.0.0.0/16", description: "vpc-a" },
59 { cidr: "10.1.0.0/16", description: "vpc-b" },
60 ],
61 });
62 return { prefixList };
63 }),
64 );
65
66 expect(prefixList.prefixListId).toMatch(/^pl-/);
67 expect(prefixList.addressFamily).toEqual("IPv4");
68 expect(prefixList.maxEntries).toEqual(5);
69
70 // Verify out-of-band.
71 const described = yield* EC2.describeManagedPrefixLists({
72 PrefixListIds: [prefixList.prefixListId],
73 });
74 expect(described.PrefixLists?.[0]?.PrefixListId).toEqual(
75 prefixList.prefixListId,
76 );
77 expect(described.PrefixLists?.[0]?.State).toEqual("create-complete");
78
79 const entries1 = yield* getEntries(prefixList.prefixListId);
80 expect(entries1).toEqual({
81 "10.0.0.0/16": "vpc-a",
82 "10.1.0.0/16": "vpc-b",
83 });
84
85 // Update entries: drop one, add one, change a description.
86 const { prefixList: updated } = yield* stack.deploy(
87 Effect.gen(function* () {
88 const prefixList = yield* PrefixList("TestPrefixList", {
89 maxEntries: 5,
90 entries: [
91 { cidr: "10.0.0.0/16", description: "vpc-a-renamed" },
92 { cidr: "10.2.0.0/16", description: "vpc-c" },
93 ],
94 });
95 return { prefixList };
96 }),
97 );
98
99 // Same list (in-place modify), version bumped.
100 expect(updated.prefixListId).toEqual(prefixList.prefixListId);
101 expect(updated.version).toBeGreaterThan(prefixList.version);
102
103 const entries2 = yield* getEntries(prefixList.prefixListId);
104 expect(entries2).toEqual({
105 "10.0.0.0/16": "vpc-a-renamed",
106 "10.2.0.0/16": "vpc-c",
107 });
108
109 yield* stack.destroy();
110 yield* assertDeleted(prefixList.prefixListId);
111 }).pipe(logLevel),
112 { tags: ["provider:aws", "provider:aws:ec2", "live"], timeout: 240_000 },
113);
114
115test.provider(
116 "list enumerates the deployed prefix list",
117 (stack) =>
118 Effect.gen(function* () {
119 yield* stack.destroy();
120
121 const { prefixList } = yield* stack.deploy(
122 Effect.gen(function* () {
123 const prefixList = yield* PrefixList("ListPrefixList", {
124 maxEntries: 3,
125 entries: [{ cidr: "192.168.0.0/24" }],
126 });
127 return { prefixList };
128 }),
129 );
130
131 const provider = yield* Provider.findProvider(PrefixList);
132 const all = yield* provider.list();
133 expect(all.some((x) => x.prefixListId === prefixList.prefixListId)).toBe(
134 true,
135 );
136
137 yield* stack.destroy();
138 yield* assertDeleted(prefixList.prefixListId);
139 }).pipe(logLevel),
140 { tags: ["provider:aws", "provider:aws:ec2", "live"], timeout: 240_000 },
141);