1import * as AWS from "@/AWS";
2import { Subnet, Vpc } from "@/AWS/EC2";
3import * as Provider from "@/Provider";
4import * as Test from "./VpcTest.ts";
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
19test.provider(
20 "create, update, delete subnet",
21 (stack) =>
22 Effect.gen(function* () {
23 yield* stack.destroy();
24
25 const { vpc, subnet } = yield* stack.deploy(
26 Effect.gen(function* () {
27 const vpc = yield* Vpc("TestVpc", {
28 cidrBlock: "10.0.0.0/16",
29 });
30 const subnet = yield* Subnet("TestSubnet", {
31 vpcId: vpc.vpcId,
32 cidrBlock: "10.0.1.0/24",
33 });
34 return { vpc, subnet };
35 }),
36 );
37
38 const actualSubnet = yield* EC2.describeSubnets({
39 SubnetIds: [subnet.subnetId],
40 });
41
42 expect(actualSubnet.Subnets?.[0]?.SubnetId).toEqual(subnet.subnetId);
43 expect(actualSubnet.Subnets?.[0]?.CidrBlock).toEqual("10.0.1.0/24");
44 expect(actualSubnet.Subnets?.[0]?.VpcId).toEqual(vpc.vpcId);
45 expect(actualSubnet.Subnets?.[0]?.State).toEqual("available");
46 expect(actualSubnet.Subnets?.[0]?.MapPublicIpOnLaunch).toEqual(false);
47
48 // Update subnet attributes
49 const { subnet: updatedSubnet } = yield* stack.deploy(
50 Effect.gen(function* () {
51 const vpc = yield* Vpc("TestVpc", {
52 cidrBlock: "10.0.0.0/16",
53 });
54 const subnet = yield* Subnet("TestSubnet", {
55 vpcId: vpc.vpcId,
56 cidrBlock: "10.0.1.0/24",
57 mapPublicIpOnLaunch: true,
58 });
59 return { vpc, subnet };
60 }),
61 );
62
63 yield* expectSubnetAttribute({
64 SubnetId: updatedSubnet.subnetId,
65 Attribute: "mapPublicIpOnLaunch",
66 Value: true,
67 });
68
69 // Delete subnet and VPC
70 yield* stack.destroy();
71
72 yield* assertSubnetDeleted(subnet.subnetId);
73 }).pipe(logLevel),
74 { tags: ["provider:aws", "provider:aws:ec2", "live"] },
75);
76
77test.provider(
78 "list enumerates the deployed subnet",
79 (stack) =>
80 Effect.gen(function* () {
81 yield* stack.destroy();
82
83 const { subnet } = yield* stack.deploy(
84 Effect.gen(function* () {
85 const vpc = yield* Vpc("ListVpc", {
86 cidrBlock: "10.0.0.0/16",
87 });
88 const subnet = yield* Subnet("ListSubnet", {
89 vpcId: vpc.vpcId,
90 cidrBlock: "10.0.1.0/24",
91 });
92 return { vpc, subnet };
93 }),
94 );
95
96 const provider = yield* Provider.findProvider(Subnet);
97 const all = yield* provider.list();
98
99 expect(all.some((s) => s.subnetId === subnet.subnetId)).toBe(true);
100
101 yield* stack.destroy();
102
103 yield* assertSubnetDeleted(subnet.subnetId);
104 }).pipe(logLevel),
105 { tags: ["provider:aws", "provider:aws:ec2", "live"] },
106);
107
108const expectSubnetAttribute = Effect.fn(function* (props: {
109 SubnetId: string;
110 Attribute: "mapPublicIpOnLaunch" | "assignIpv6AddressOnCreation";
111 Value: boolean;
112}) {
113 yield* EC2.describeSubnets({
114 SubnetIds: [props.SubnetId],
115 }).pipe(
116 Effect.tap(Effect.logDebug),
117 Effect.flatMap((result) => {
118 const subnet = result.Subnets?.[0];
119 const actualValue =
120 props.Attribute === "mapPublicIpOnLaunch"
121 ? subnet?.MapPublicIpOnLaunch
122 : subnet?.AssignIpv6AddressOnCreation;
123
124 return actualValue === props.Value
125 ? Effect.succeed(result)
126 : Effect.fail(new SubnetAttributeStale());
127 }),
128 Effect.retry({
129 while: (e) => e instanceof SubnetAttributeStale,
130 schedule: Schedule.max([Schedule.exponential(100), Schedule.recurs(8)]),
131 }),
132 );
133});
134
135const assertSubnetDeleted = Effect.fn(function* (subnetId: string) {
136 yield* EC2.describeSubnets({
137 SubnetIds: [subnetId],
138 }).pipe(
139 Effect.flatMap(() => Effect.fail(new SubnetStillExists())),
140 Effect.retry({
141 while: (e) => e instanceof SubnetStillExists,
142 schedule: Schedule.max([Schedule.exponential(100), Schedule.recurs(8)]),
143 }),
144 Effect.catchTag("InvalidSubnetID.NotFound", () => Effect.void),
145 );
146});
147
148class SubnetStillExists extends Data.TaggedError("SubnetStillExists") {}
149
150class SubnetAttributeStale extends Data.TaggedError("SubnetAttributeStale") {}