1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as baremetalsolution from "@distilled.cloud/gcp/baremetalsolution_v2";
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// Bare Metal Solution is contracted physical hardware (real cost) and the API
18// is off on the testing project (ServiceDisabled: "Bare Metal Solution API has not
19// been used in project ... or it is disabled."). Set
20// GCP_TEST_BAREMETALSOLUTION_VOLUME to a provisioned boot volume on an
21// entitled project to run the lifecycle.
22const bootVolume = process.env.GCP_TEST_BAREMETALSOLUTION_VOLUME;
23const runLifecycle = !!bootVolume;
24
25const missingVolume = (projectId: string) =>
26 `projects/${projectId}/locations/us-central1/volumes/alchemy-missing-boot`;
27
28const waitUntilGone = (name: string) =>
29 baremetalsolution.getProjectsLocationsVolumesSnapshots({ name }).pipe(
30 Effect.as("found" as const),
31 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
32 Effect.repeat({
33 schedule: Schedule.spaced("2 seconds"),
34 until: (status) => status === "gone",
35 times: 10,
36 }),
37 );
38
39test.provider(
40 "getProjectsLocationsVolumesSnapshots on a missing snapshot fails with a typed tag",
41 (stack) =>
42 Effect.gen(function* () {
43 const { project } = yield* GcpEnvironment.current;
44 yield* stack.destroy();
45
46 const error = yield* Effect.flip(
47 baremetalsolution.getProjectsLocationsVolumesSnapshots({
48 name: `${missingVolume(project)}/snapshots/alchemy-bms-snap-missing`,
49 }),
50 );
51 expect(error._tag).toEqual("ServiceDisabled");
52
53 yield* stack.destroy();
54 }).pipe(logLevel),
55 {
56 tags: ["provider:gcp", "provider:gcp:baremetalsolution", "live"],
57 timeout: 90_000,
58 },
59);
60
61test.provider.skipIf(runLifecycle)(
62 "create is rejected with ServiceDisabled when the Bare Metal Solution API is disabled",
63 (stack) =>
64 Effect.gen(function* () {
65 const { project } = yield* GcpEnvironment.current;
66 yield* stack.destroy();
67
68 const error = yield* Effect.flip(
69 stack.deploy(
70 Effect.gen(function* () {
71 return yield* GCP.BareMetalSolution.VolumesSnapshot("Nightly", {
72 volume: missingVolume(project),
73 description: "alchemy-test-snap",
74 });
75 }),
76 ),
77 );
78 expect(error._tag).toEqual("ServiceDisabled");
79
80 yield* stack.destroy();
81 }).pipe(logLevel),
82 {
83 tags: ["provider:gcp", "provider:gcp:baremetalsolution", "live"],
84 timeout: 90_000,
85 },
86);
87
88test.provider.skipIf(!runLifecycle)(
89 "create and delete a boot volume snapshot",
90 (stack) =>
91 Effect.gen(function* () {
92 yield* stack.destroy();
93
94 const created = yield* stack.deploy(
95 Effect.gen(function* () {
96 return yield* GCP.BareMetalSolution.VolumesSnapshot("Nightly", {
97 volume: bootVolume!,
98 description: "alchemy-test-snap",
99 });
100 }),
101 );
102
103 expect(created.name).toContain("/snapshots/");
104 expect(created.snapshotId).toEqual(expect.any(String));
105 expect(created.description).toEqual("alchemy-test-snap");
106
107 const fetched =
108 yield* baremetalsolution.getProjectsLocationsVolumesSnapshots({
109 name: created.name,
110 });
111 expect(fetched.name).toEqual(created.name);
112 expect(fetched.description).toContain("alchemy-id=");
113 expect(fetched.description).toContain("alchemy-test-snap");
114
115 yield* stack.destroy();
116
117 const gone = yield* waitUntilGone(created.name);
118 expect(gone).toEqual("gone");
119 }).pipe(logLevel),
120 {
121 tags: ["provider:gcp", "provider:gcp:baremetalsolution", "live"],
122 timeout: 120_000,
123 },
124);