1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as compute from "@distilled.cloud/gcp/compute_v1";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import { MinimumLogLevel } from "effect/References";
7import * as Schedule from "effect/Schedule";
8
9const { test } = Test.make({ providers: GCP.providers() });
10
11const logLevel = Effect.provideService(
12 MinimumLogLevel,
13 process.env.DEBUG ? "Debug" : "Info",
14);
15
16const waitUntilGone = (project: string, firewall: string) =>
17 compute.getFirewalls({ project, firewall }).pipe(
18 Effect.as("found" as const),
19 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
20 Effect.repeat({
21 schedule: Schedule.spaced("1 second"),
22 until: (status) => status === "gone",
23 times: 10,
24 }),
25 );
26
27test.provider.skipIf(!!process.env.FAST)(
28 "create, update, replace, and delete a firewall",
29 (stack) =>
30 Effect.gen(function* () {
31 yield* stack.destroy();
32
33 const created = yield* stack.deploy(
34 Effect.gen(function* () {
35 return yield* GCP.Compute.Firewall("AllowHttp", {
36 description: "test http",
37 allowed: [{ protocol: "tcp", ports: ["80"] }],
38 sourceRanges: ["10.0.0.0/8"],
39 targetTags: ["alchemy-fw"],
40 });
41 }),
42 );
43
44 expect(created.firewallName).toEqual(expect.any(String));
45 expect(created.direction).toEqual("INGRESS");
46 expect(created.disabled).toEqual(false);
47 expect(created.priority).toEqual(1000);
48 expect(created.description).toEqual("test http");
49 expect(created.allowed).toEqual(
50 expect.arrayContaining([
51 expect.objectContaining({ protocol: "tcp", ports: ["80"] }),
52 ]),
53 );
54 expect(created.sourceRanges).toEqual(["10.0.0.0/8"]);
55 expect(created.targetTags).toEqual(["alchemy-fw"]);
56 expect(created.network).toEqual(expect.stringContaining("networks/"));
57
58 const fetched = yield* compute.getFirewalls({
59 project: created.project,
60 firewall: created.firewallName,
61 });
62 expect(fetched.name).toEqual(created.firewallName);
63 expect(fetched.description).toContain("alchemy-id=");
64 expect(fetched.description).toContain("test http");
65 expect(fetched.allowed?.[0]?.IPProtocol).toEqual("tcp");
66 expect(fetched.sourceRanges).toEqual(["10.0.0.0/8"]);
67
68 const updated = yield* stack.deploy(
69 Effect.gen(function* () {
70 return yield* GCP.Compute.Firewall("AllowHttp", {
71 firewallName: created.firewallName,
72 description: "updated http",
73 allowed: [{ protocol: "tcp", ports: ["80", "443"] }],
74 sourceRanges: ["10.0.0.0/8"],
75 targetTags: ["alchemy-fw", "web"],
76 priority: 800,
77 disabled: true,
78 });
79 }),
80 );
81
82 expect(updated.firewallName).toEqual(created.firewallName);
83 expect(updated.id).toEqual(created.id);
84 expect(updated.description).toEqual("updated http");
85 expect(updated.priority).toEqual(800);
86 expect(updated.disabled).toEqual(true);
87 expect(updated.targetTags.sort()).toEqual(["alchemy-fw", "web"].sort());
88 expect(updated.allowed[0]?.ports?.sort()).toEqual(["443", "80"]);
89
90 const fetchedUpdate = yield* compute.getFirewalls({
91 project: updated.project,
92 firewall: updated.firewallName,
93 });
94 expect(fetchedUpdate.priority).toEqual(800);
95 expect(fetchedUpdate.disabled).toEqual(true);
96
97 const replaced = yield* stack.deploy(
98 Effect.gen(function* () {
99 return yield* GCP.Compute.Firewall("AllowHttp", {
100 firewallName: created.firewallName,
101 description: "egress https",
102 direction: "EGRESS",
103 allowed: [{ protocol: "tcp", ports: ["443"] }],
104 destinationRanges: ["10.0.0.0/8"],
105 targetTags: ["alchemy-fw"],
106 });
107 }),
108 );
109
110 expect(replaced.firewallName).toEqual(created.firewallName);
111 expect(replaced.direction).toEqual("EGRESS");
112 expect(replaced.destinationRanges).toEqual(["10.0.0.0/8"]);
113 expect(replaced.id).not.toEqual(created.id);
114
115 const fetchedReplace = yield* compute.getFirewalls({
116 project: replaced.project,
117 firewall: replaced.firewallName,
118 });
119 expect((fetchedReplace.direction ?? "").toUpperCase()).toEqual("EGRESS");
120
121 yield* stack.destroy();
122
123 const gone = yield* waitUntilGone(created.project, created.firewallName);
124 expect(gone).toEqual("gone");
125 }).pipe(logLevel),
126 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 180_000 },
127);