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
17const zone = "us-central1-a";
18
19const waitUntilGone = (instanceGroup: string) =>
20 GcpEnvironment.current.pipe(
21 Effect.flatMap(({ project }) =>
22 compute.getInstanceGroups({ project, zone, instanceGroup }).pipe(
23 Effect.as("found" as const),
24 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
25 Effect.repeat({
26 schedule: Schedule.spaced("1 second"),
27 until: (status) => status === "gone",
28 times: 10,
29 }),
30 ),
31 ),
32 );
33
34test.provider(
35 "create, update, replace, and delete an instance group",
36 (stack) =>
37 Effect.gen(function* () {
38 const { project } = yield* GcpEnvironment.current;
39 yield* stack.destroy();
40
41 const created = yield* stack.deploy(
42 Effect.gen(function* () {
43 return yield* GCP.Compute.InstanceGroup("Web", {
44 zone,
45 description: "unmanaged backends",
46 namedPorts: [{ name: "http", port: 80 }],
47 });
48 }),
49 );
50
51 expect(created.instanceGroupName).toEqual(expect.any(String));
52 expect(created.zone).toEqual(zone);
53 expect(created.description).toEqual("unmanaged backends");
54 expect(created.namedPorts).toEqual([{ name: "http", port: 80 }]);
55 expect(created.size).toEqual(0);
56
57 const fetched = yield* compute.getInstanceGroups({
58 project,
59 zone,
60 instanceGroup: created.instanceGroupName,
61 });
62 expect(fetched.name).toEqual(created.instanceGroupName);
63 expect(fetched.description).toContain("[alchemy ");
64 expect(fetched.description).toContain("unmanaged backends");
65 expect(fetched.namedPorts?.some((port) => port.name === "http")).toEqual(
66 true,
67 );
68
69 const updated = yield* stack.deploy(
70 Effect.gen(function* () {
71 return yield* GCP.Compute.InstanceGroup("Web", {
72 instanceGroupName: created.instanceGroupName,
73 zone,
74 description: "unmanaged backends",
75 namedPorts: [
76 { name: "http", port: 80 },
77 { name: "https", port: 443 },
78 ],
79 });
80 }),
81 );
82
83 expect(updated.instanceGroupName).toEqual(created.instanceGroupName);
84 expect(updated.id).toEqual(created.id);
85 expect(updated.namedPorts).toEqual([
86 { name: "http", port: 80 },
87 { name: "https", port: 443 },
88 ]);
89
90 const refetched = yield* compute.getInstanceGroups({
91 project,
92 zone,
93 instanceGroup: updated.instanceGroupName,
94 });
95 expect(
96 (refetched.namedPorts ?? []).map((port) => port.name).sort(),
97 ).toEqual(["http", "https"]);
98
99 const replaced = yield* stack.deploy(
100 Effect.gen(function* () {
101 return yield* GCP.Compute.InstanceGroup("Web", {
102 instanceGroupName: created.instanceGroupName,
103 zone,
104 description: "replaced backends",
105 namedPorts: [
106 { name: "http", port: 80 },
107 { name: "https", port: 443 },
108 ],
109 });
110 }),
111 );
112
113 expect(replaced.instanceGroupName).toEqual(created.instanceGroupName);
114 expect(replaced.description).toEqual("replaced backends");
115 expect(replaced.id).not.toEqual(created.id);
116
117 const replacedFetched = yield* compute.getInstanceGroups({
118 project,
119 zone,
120 instanceGroup: replaced.instanceGroupName,
121 });
122 expect(replacedFetched.description).toContain("replaced backends");
123 expect(replacedFetched.id).toEqual(replaced.id);
124
125 yield* stack.destroy();
126
127 const gone = yield* waitUntilGone(created.instanceGroupName);
128 expect(gone).toEqual("gone");
129 }).pipe(logLevel),
130 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 90_000 },
131);