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