1import * as AWS from "@/AWS";
2import { 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 vpc",
21 (stack) =>
22 Effect.gen(function* () {
23 yield* stack.destroy();
24
25 const vpc = yield* stack.deploy(
26 Effect.gen(function* () {
27 return yield* Vpc("TestVpc", {
28 cidrBlock: "10.0.0.0/16",
29 enableDnsSupport: true,
30 enableDnsHostnames: true,
31 });
32 }),
33 );
34
35 const actualVpc = yield* EC2.describeVpcs({
36 VpcIds: [vpc.vpcId],
37 });
38 expect(actualVpc.Vpcs?.[0]?.VpcId).toEqual(vpc.vpcId);
39 expect(actualVpc.Vpcs?.[0]?.CidrBlock).toEqual("10.0.0.0/16");
40 expect(actualVpc.Vpcs?.[0]?.State).toEqual("available");
41
42 yield* expectVpcAttribute({
43 VpcId: vpc.vpcId,
44 Attribute: "enableDnsSupport",
45 Value: true,
46 });
47
48 yield* expectVpcAttribute({
49 VpcId: vpc.vpcId,
50 Attribute: "enableDnsHostnames",
51 Value: true,
52 });
53
54 // Update VPC attributes
55 const updatedVpc = yield* stack.deploy(
56 Effect.gen(function* () {
57 return yield* Vpc("TestVpc", {
58 cidrBlock: "10.0.0.0/16",
59 enableDnsSupport: false,
60 enableDnsHostnames: false,
61 });
62 }),
63 );
64
65 yield* expectVpcAttribute({
66 VpcId: updatedVpc.vpcId,
67 Attribute: "enableDnsSupport",
68 Value: false,
69 });
70
71 yield* expectVpcAttribute({
72 VpcId: updatedVpc.vpcId,
73 Attribute: "enableDnsHostnames",
74 Value: false,
75 });
76
77 yield* stack.destroy();
78
79 yield* assertVpcDeleted(vpc.vpcId);
80 }).pipe(logLevel),
81 { tags: ["provider:aws", "provider:aws:ec2", "live"], timeout: 15 * 60_000 },
82);
83
84test.provider(
85 "list enumerates the deployed vpc",
86 (stack) =>
87 Effect.gen(function* () {
88 yield* stack.destroy();
89
90 const deployed = yield* stack.deploy(
91 Effect.gen(function* () {
92 return yield* Vpc("ListVpc", {
93 cidrBlock: "10.0.0.0/16",
94 });
95 }),
96 );
97
98 const provider = yield* Provider.findProvider(Vpc);
99 const all = yield* provider.list();
100
101 expect(all.some((v) => v.vpcId === deployed.vpcId)).toBe(true);
102
103 yield* stack.destroy();
104
105 yield* assertVpcDeleted(deployed.vpcId);
106 }).pipe(logLevel),
107 { tags: ["provider:aws", "provider:aws:ec2", "live"], timeout: 15 * 60_000 },
108);
109
110const expectVpcAttribute = Effect.fn(function* (props: {
111 VpcId: string;
112 Attribute: EC2.VpcAttributeName;
113 Value: boolean;
114}) {
115 yield* EC2.describeVpcAttribute({
116 VpcId: props.VpcId,
117 Attribute: props.Attribute,
118 }).pipe(
119 Effect.tap(Effect.logDebug),
120 Effect.flatMap((result: any) =>
121 result[`${props.Attribute[0].toUpperCase()}${props.Attribute.slice(1)}`]
122 ?.Value === props.Value
123 ? Effect.succeed(result)
124 : Effect.fail(new VpcAttributeStale()),
125 ),
126 Effect.retry({
127 while: (e) => e._tag === "VpcAttributeStale",
128 schedule: Schedule.max([Schedule.exponential(100), Schedule.recurs(8)]),
129 }),
130 );
131});
132
133class VpcAttributeStale extends Data.TaggedError("VpcAttributeStale") {}
134
135class VpcStillExists extends Data.TaggedError("VpcStillExists") {}
136
137export const assertVpcDeleted = Effect.fn(function* (vpcId: string) {
138 yield* EC2.describeVpcs({
139 VpcIds: [vpcId],
140 }).pipe(
141 Effect.flatMap(() => Effect.fail(new VpcStillExists())),
142 Effect.retry({
143 while: (e) => e._tag === "VpcStillExists",
144 schedule: Schedule.max([Schedule.exponential(100), Schedule.recurs(8)]),
145 }),
146 Effect.catchTag("InvalidVpcID.NotFound", () => Effect.void),
147 );
148});