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 region = "us-central1";
18
19const waitUntilGone = (managerName: string) =>
20 GcpEnvironment.current.pipe(
21 Effect.flatMap(({ project }) =>
22 compute
23 .getRegionInstanceGroupManagers({
24 project,
25 region,
26 instanceGroupManager: managerName,
27 })
28 .pipe(
29 Effect.as("found" as const),
30 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
31 Effect.repeat({
32 schedule: Schedule.spaced("2 seconds"),
33 until: (status) => status === "gone",
34 times: 15,
35 }),
36 ),
37 ),
38 );
39
40const templateProps: GCP.Compute.InstanceTemplateProps = {
41 machineType: "e2-micro",
42 disks: [
43 {
44 boot: true,
45 autoDelete: true,
46 sourceImage: "projects/debian-cloud/global/images/family/debian-12",
47 diskSizeGb: 10,
48 },
49 ],
50 networkInterfaces: [{ network: "global/networks/default" }],
51};
52
53test.provider(
54 "create, update, replace, and delete a regional instance group manager",
55 (stack) =>
56 Effect.gen(function* () {
57 const { project } = yield* GcpEnvironment.current;
58 yield* stack.destroy();
59
60 const created = yield* stack.deploy(
61 Effect.gen(function* () {
62 const template = yield* GCP.Compute.InstanceTemplate(
63 "Web",
64 templateProps,
65 );
66 const manager = yield* GCP.Compute.RegionInstanceGroupManager(
67 "WebMig",
68 {
69 region,
70 instanceTemplate: template.selfLink.as<string>(),
71 baseInstanceName: "web",
72 targetSize: 0,
73 description: "regional mig",
74 namedPorts: [{ name: "http", port: 80 }],
75 },
76 );
77 return { template, manager };
78 }),
79 );
80
81 expect(created.manager.managerName).toEqual(expect.any(String));
82 expect(created.manager.region).toEqual(region);
83 expect(created.manager.targetSize).toEqual(0);
84 expect(created.manager.baseInstanceName).toEqual("web");
85 expect(created.manager.description).toEqual("regional mig");
86 expect(created.manager.namedPorts).toEqual([{ name: "http", port: 80 }]);
87 expect(created.manager.instanceTemplate).toEqual(expect.any(String));
88 expect(created.manager.project).toEqual(project);
89
90 const fetched = yield* compute.getRegionInstanceGroupManagers({
91 project,
92 region,
93 instanceGroupManager: created.manager.managerName,
94 });
95 expect(fetched.name).toEqual(created.manager.managerName);
96 expect(fetched.description).toContain("[alchemy ");
97 expect(fetched.description).toContain("regional mig");
98 expect(fetched.targetSize).toEqual(0);
99 expect(fetched.baseInstanceName).toEqual("web");
100 expect(
101 (fetched.namedPorts ?? []).some((port) => port.name === "http"),
102 ).toEqual(true);
103
104 const updated = yield* stack.deploy(
105 Effect.gen(function* () {
106 const template = yield* GCP.Compute.InstanceTemplate(
107 "Web",
108 templateProps,
109 );
110 const manager = yield* GCP.Compute.RegionInstanceGroupManager(
111 "WebMig",
112 {
113 managerName: created.manager.managerName,
114 region,
115 instanceTemplate: template.selfLink.as<string>(),
116 baseInstanceName: "web",
117 targetSize: 0,
118 description: "regional mig",
119 namedPorts: [
120 { name: "http", port: 80 },
121 { name: "https", port: 443 },
122 ],
123 },
124 );
125 return { template, manager };
126 }),
127 );
128
129 expect(updated.manager.managerName).toEqual(created.manager.managerName);
130 expect(updated.manager.managerId).toEqual(created.manager.managerId);
131 expect(updated.manager.namedPorts).toEqual([
132 { name: "http", port: 80 },
133 { name: "https", port: 443 },
134 ]);
135
136 const refetched = yield* compute.getRegionInstanceGroupManagers({
137 project,
138 region,
139 instanceGroupManager: updated.manager.managerName,
140 });
141 expect(
142 (refetched.namedPorts ?? []).map((port) => port.name).sort(),
143 ).toEqual(["http", "https"]);
144
145 const replaced = yield* stack.deploy(
146 Effect.gen(function* () {
147 const template = yield* GCP.Compute.InstanceTemplate(
148 "Web",
149 templateProps,
150 );
151 const manager = yield* GCP.Compute.RegionInstanceGroupManager(
152 "WebMig",
153 {
154 managerName: created.manager.managerName,
155 region,
156 instanceTemplate: template.selfLink.as<string>(),
157 baseInstanceName: "app",
158 targetSize: 0,
159 description: "replaced mig",
160 namedPorts: [
161 { name: "http", port: 80 },
162 { name: "https", port: 443 },
163 ],
164 },
165 );
166 return { template, manager };
167 }),
168 );
169
170 expect(replaced.manager.managerName).toEqual(created.manager.managerName);
171 expect(replaced.manager.baseInstanceName).toEqual("app");
172 expect(replaced.manager.description).toEqual("replaced mig");
173 expect(replaced.manager.managerId).not.toEqual(created.manager.managerId);
174
175 const replacedFetched = yield* compute.getRegionInstanceGroupManagers({
176 project,
177 region,
178 instanceGroupManager: replaced.manager.managerName,
179 });
180 expect(replacedFetched.baseInstanceName).toEqual("app");
181 expect(replacedFetched.description).toContain("replaced mig");
182 expect(replacedFetched.id).toEqual(replaced.manager.managerId);
183
184 yield* stack.destroy();
185
186 const gone = yield* waitUntilGone(created.manager.managerName);
187 expect(gone).toEqual("gone");
188 }).pipe(logLevel),
189 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 180_000 },
190);