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";
9import { DEFAULT_NETWORK } from "../networkQuota.ts";
10
11const { test } = Test.make({ providers: GCP.providers() });
12
13const logLevel = Effect.provideService(
14 MinimumLogLevel,
15 process.env.DEBUG ? "Debug" : "Info",
16);
17
18const region = "us-central1";
19
20const waitUntilGone = (project: string, networkAttachment: string) =>
21 compute
22 .getNetworkAttachments({
23 project,
24 region,
25 networkAttachment,
26 })
27 .pipe(
28 Effect.as("found" as const),
29 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
30 Effect.repeat({
31 schedule: Schedule.spaced("2 seconds"),
32 until: (status) => status === "gone",
33 times: 10,
34 }),
35 );
36
37test.provider(
38 "getNetworkAttachments on a missing attachment fails with NotFound",
39 (stack) =>
40 Effect.gen(function* () {
41 yield* stack.destroy();
42 const { project } = yield* GcpEnvironment.current;
43 const error = yield* Effect.flip(
44 compute.getNetworkAttachments({
45 project,
46 region,
47 networkAttachment: "alchemy-missing-na",
48 }),
49 );
50 expect(error._tag).toBe("NotFound");
51 yield* stack.destroy();
52 }).pipe(logLevel),
53 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 90_000 },
54);
55
56test.provider(
57 "create, update, and delete a network attachment",
58 (stack) =>
59 Effect.gen(function* () {
60 yield* stack.destroy();
61
62 const created = yield* stack.deploy(
63 Effect.gen(function* () {
64 const subnet = yield* GCP.Compute.Subnetwork("ConsumerSubnet", {
65 network: DEFAULT_NETWORK,
66 region,
67 ipCidrRange: "172.20.0.0/24",
68 });
69 const attachment = yield* GCP.Compute.NetworkAttachment(
70 "ConsumerAttachment",
71 {
72 region,
73 subnetworks: [subnet.selfLink.as<string>()],
74 connectionPreference: "ACCEPT_AUTOMATIC",
75 description: "psc consumer",
76 },
77 );
78 return { subnet, attachment };
79 }),
80 );
81
82 expect(created.attachment.networkAttachmentName.length).toBeGreaterThan(
83 0,
84 );
85 expect(created.attachment.region).toEqual(region);
86 expect(created.attachment.connectionPreference).toEqual(
87 "ACCEPT_AUTOMATIC",
88 );
89 expect(created.attachment.description).toEqual("psc consumer");
90 expect(created.attachment.subnetworks.length).toBeGreaterThan(0);
91 expect(created.attachment.selfLink).toContain("/networkAttachments/");
92
93 const fetched = yield* compute.getNetworkAttachments({
94 project: created.attachment.project,
95 region,
96 networkAttachment: created.attachment.networkAttachmentName,
97 });
98 expect(fetched.name).toEqual(created.attachment.networkAttachmentName);
99 expect(fetched.connectionPreference).toEqual("ACCEPT_AUTOMATIC");
100 expect(fetched.description).toContain("[alchemy ");
101 expect(fetched.description).toContain("psc consumer");
102
103 const updated = yield* stack.deploy(
104 Effect.gen(function* () {
105 const subnet = yield* GCP.Compute.Subnetwork("ConsumerSubnet", {
106 subnetworkName: created.subnet.subnetworkName,
107 network: DEFAULT_NETWORK,
108 region,
109 ipCidrRange: "172.20.0.0/24",
110 });
111 return yield* GCP.Compute.NetworkAttachment("ConsumerAttachment", {
112 networkAttachmentName: created.attachment.networkAttachmentName,
113 region,
114 subnetworks: [subnet.selfLink.as<string>()],
115 connectionPreference: "ACCEPT_MANUAL",
116 producerAcceptLists: [created.attachment.project],
117 description: "psc consumer updated",
118 });
119 }),
120 );
121
122 expect(updated.networkAttachmentName).toEqual(
123 created.attachment.networkAttachmentName,
124 );
125 expect(updated.connectionPreference).toEqual("ACCEPT_MANUAL");
126 expect(updated.description).toEqual("psc consumer updated");
127 expect(updated.producerAcceptLists).toEqual(
128 expect.arrayContaining([created.attachment.project]),
129 );
130
131 const fetchedUpdated = yield* compute.getNetworkAttachments({
132 project: updated.project,
133 region,
134 networkAttachment: updated.networkAttachmentName,
135 });
136 expect(fetchedUpdated.connectionPreference).toEqual("ACCEPT_MANUAL");
137 expect(fetchedUpdated.description).toContain("psc consumer updated");
138
139 yield* stack.destroy();
140
141 const gone = yield* waitUntilGone(
142 created.attachment.project,
143 created.attachment.networkAttachmentName,
144 );
145 expect(gone).toEqual("gone");
146 }).pipe(logLevel),
147 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 180_000 },
148);