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";
8import { GcpEnvironment } from "@/GCP/Environment";
9
10const { test } = Test.make({ providers: GCP.providers() });
11
12const logLevel = Effect.provideService(
13 MinimumLogLevel,
14 process.env.DEBUG ? "Debug" : "Info",
15);
16
17// Needs Cloud Armor Managed Protection Plus (GCP_TEST_CLOUD_ARMOR=1); without
18// it insert fails with `BadRequest: Network Security Policies require Cloud
19// Armor Managed Protection Plus tier and above to use.`
20const runLifecycle = !!process.env.GCP_TEST_CLOUD_ARMOR && !process.env.FAST;
21
22const region = "us-central1";
23
24const waitUntilGone = (networkEdgeSecurityService: string) =>
25 GcpEnvironment.current.pipe(
26 Effect.flatMap(({ project }) =>
27 compute
28 .getNetworkEdgeSecurityServices({
29 project,
30 region,
31 networkEdgeSecurityService,
32 })
33 .pipe(
34 Effect.as("found" as const),
35 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
36 Effect.repeat({
37 schedule: Schedule.spaced("1 second"),
38 until: (status) => status === "gone",
39 times: 10,
40 }),
41 ),
42 ),
43 );
44
45test.provider(
46 "getNetworkEdgeSecurityServices on a missing service fails with NotFound",
47 (stack) =>
48 Effect.gen(function* () {
49 const { project } = yield* GcpEnvironment.current;
50 yield* stack.destroy();
51
52 const error = yield* Effect.flip(
53 compute.getNetworkEdgeSecurityServices({
54 project,
55 region,
56 networkEdgeSecurityService: "alchemy-missing-ness",
57 }),
58 );
59 expect(error._tag).toBe("NotFound");
60
61 yield* stack.destroy();
62 }).pipe(logLevel),
63 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 90_000 },
64);
65
66test.provider(
67 "insertNetworkEdgeSecurityServices without Cloud Armor Enterprise fails with CloudArmorEnterpriseRequired",
68 () =>
69 Effect.gen(function* () {
70 const { project } = yield* GcpEnvironment.current;
71 const error = yield* Effect.flip(
72 compute.insertNetworkEdgeSecurityServices({
73 project,
74 region,
75 body: {
76 name: "alchemy-ness-probe",
77 description: "alchemy entitlement probe",
78 },
79 }),
80 );
81 expect(error._tag).toEqual("CloudArmorEnterpriseRequired");
82 }).pipe(logLevel),
83 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 60_000 },
84);
85
86test.provider.skipIf(!runLifecycle)(
87 "create, update, and delete a network edge security service",
88 (stack) =>
89 Effect.gen(function* () {
90 yield* stack.destroy();
91
92 const created = yield* stack.deploy(
93 Effect.gen(function* () {
94 return yield* GCP.Compute.NetworkEdgeSecurityService("EdgeArmor", {
95 region,
96 description: "regional network armor",
97 });
98 }),
99 );
100
101 expect(created.networkEdgeSecurityServiceName).toEqual(
102 expect.any(String),
103 );
104 expect(created.region).toEqual(region);
105 expect(created.description).toEqual("regional network armor");
106
107 const fetched = yield* compute.getNetworkEdgeSecurityServices({
108 project: created.project,
109 region,
110 networkEdgeSecurityService: created.networkEdgeSecurityServiceName,
111 });
112 expect(fetched.name).toEqual(created.networkEdgeSecurityServiceName);
113 expect(fetched.description).toContain("[alchemy ");
114
115 const updated = yield* stack.deploy(
116 Effect.gen(function* () {
117 return yield* GCP.Compute.NetworkEdgeSecurityService("EdgeArmor", {
118 networkEdgeSecurityServiceName:
119 created.networkEdgeSecurityServiceName,
120 region,
121 description: "updated network armor",
122 });
123 }),
124 );
125 expect(updated.description).toEqual("updated network armor");
126
127 yield* stack.destroy();
128 const gone = yield* waitUntilGone(created.networkEdgeSecurityServiceName);
129 expect(gone).toEqual("gone");
130 }).pipe(logLevel),
131 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 90_000 },
132);