1import * as AWS from "@/AWS";
2import { Snapshot, Volume } from "@/AWS/EC2";
3import * as Test from "@/Test/Alchemy";
4import * as EC2 from "@distilled.cloud/aws/ec2";
5import { expect } from "alchemy-test";
6import * as Data from "effect/Data";
7import * as Effect from "effect/Effect";
8import { MinimumLogLevel } from "effect/References";
9import * as Schedule from "effect/Schedule";
10
11const { test } = Test.make({ providers: AWS.providers() });
12
13const logLevel = Effect.provideService(
14 MinimumLogLevel,
15 process.env.DEBUG ? "Debug" : "Info",
16);
17
18const AZ = "us-west-2a";
19
20test.provider(
21 "snapshot a volume, wait for completion, then delete both",
22 (stack) =>
23 Effect.gen(function* () {
24 yield* stack.destroy();
25
26 const { volume, snapshot } = yield* stack.deploy(
27 Effect.gen(function* () {
28 const volume = yield* Volume("SnapSourceVolume", {
29 availabilityZone: AZ,
30 size: 1,
31 volumeType: "gp3",
32 });
33 const snapshot = yield* Snapshot("TestSnapshot", {
34 volumeId: volume.volumeId,
35 description: "alchemy storage-trio test snapshot",
36 });
37 return { volume, snapshot };
38 }),
39 );
40
41 expect(snapshot.snapshotId).toMatch(/^snap-/);
42 expect(snapshot.volumeId).toBe(volume.volumeId);
43 // The provider waits for completion before returning.
44 expect(snapshot.state).toBe("completed");
45
46 // Out-of-band verification.
47 const observed = yield* EC2.describeSnapshots({
48 SnapshotIds: [snapshot.snapshotId],
49 });
50 const s = observed.Snapshots?.[0];
51 expect(s?.State).toBe("completed");
52 expect(s?.VolumeId).toBe(volume.volumeId);
53
54 yield* stack.destroy();
55 yield* assertSnapshotDeleted(snapshot.snapshotId);
56 }).pipe(logLevel),
57 { tags: ["provider:aws", "provider:aws:ec2", "live"], timeout: 180_000 },
58);
59
60const assertSnapshotDeleted = Effect.fn(function* (snapshotId: string) {
61 yield* EC2.describeSnapshots({ SnapshotIds: [snapshotId] }).pipe(
62 Effect.flatMap(() => Effect.fail(new SnapshotStillExists())),
63 Effect.retry({
64 while: (e) => e instanceof SnapshotStillExists,
65 schedule: Schedule.max([Schedule.exponential(200), Schedule.recurs(8)]),
66 }),
67 Effect.catchTag("InvalidSnapshot.NotFound", () => Effect.void),
68 );
69});
70
71class SnapshotStillExists extends Data.TaggedError("SnapshotStillExists") {}