1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as networksecurity from "@distilled.cloud/gcp/networksecurity_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 { withNetworkSlots } 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 waitUntilGone = (name: string) =>
19 networksecurity
20 .getProjectsLocationsInterceptEndpointGroupAssociations({ name })
21 .pipe(
22 Effect.as("found" as const),
23 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
24 Effect.repeat({
25 schedule: Schedule.spaced("2 seconds"),
26 until: (status) => status === "gone",
27 times: 10,
28 }),
29 );
30
31test.provider(
32 "getProjectsLocationsInterceptEndpointGroupAssociations on a missing association fails with a typed tag",
33 (stack) =>
34 Effect.gen(function* () {
35 yield* stack.destroy();
36
37 const { project } = yield* GcpEnvironment.current;
38 const error = yield* Effect.flip(
39 networksecurity.getProjectsLocationsInterceptEndpointGroupAssociations({
40 name: `projects/${project}/locations/global/interceptEndpointGroupAssociations/alchemy-missing`,
41 }),
42 );
43 expect(error._tag).toEqual("NotFound");
44
45 yield* stack.destroy();
46 }).pipe(logLevel),
47 {
48 tags: ["provider:gcp", "provider:gcp:networksecurity", "live"],
49 timeout: 90_000,
50 },
51);
52
53test.provider.skipIf(!!process.env.FAST)(
54 "create, update, and delete an intercept endpoint group association",
55 (stack) =>
56 Effect.gen(function* () {
57 yield* stack.destroy();
58
59 const created = yield* stack.deploy(
60 Effect.gen(function* () {
61 const producer = yield* GCP.Compute.Network("Producer", {
62 autoCreateSubnetworks: false,
63 description: "intercept producer vpc",
64 });
65 const consumer = yield* GCP.Compute.Network("Consumer", {
66 autoCreateSubnetworks: false,
67 description: "intercept consumer vpc",
68 });
69 const collectors =
70 yield* GCP.NetworkSecurity.InterceptDeploymentGroup("Inspect", {
71 network: producer.selfLink.as<string>(),
72 description: "collectors",
73 });
74 const endpoints = yield* GCP.NetworkSecurity.InterceptEndpointGroup(
75 "Front",
76 {
77 interceptDeploymentGroup: collectors.name,
78 },
79 );
80 const association =
81 yield* GCP.NetworkSecurity.InterceptEndpointGroupAssociation(
82 "Link",
83 {
84 interceptEndpointGroup: endpoints.name,
85 network: consumer.selfLink.as<string>(),
86 labels: { env: "test" },
87 },
88 );
89 return { producer, consumer, collectors, endpoints, association };
90 }),
91 );
92
93 expect(created.association.name).toContain(
94 "/interceptEndpointGroupAssociations/",
95 );
96 expect(created.association.location).toEqual("global");
97 expect(created.association.labels).toMatchObject({ env: "test" });
98 expect(created.association.interceptEndpointGroup).toEqual(
99 created.endpoints.name,
100 );
101 expect(created.association.networkName).toEqual(
102 created.consumer.networkName,
103 );
104
105 const fetched =
106 yield* networksecurity.getProjectsLocationsInterceptEndpointGroupAssociations(
107 {
108 name: created.association.name,
109 },
110 );
111 expect(fetched.name).toEqual(created.association.name);
112 expect(fetched.labels?.env).toEqual("test");
113 expect(
114 Object.keys(fetched.labels ?? {}).some((key) =>
115 key.startsWith("alchemy-"),
116 ),
117 ).toEqual(true);
118
119 const updated = yield* stack.deploy(
120 Effect.gen(function* () {
121 const producer = yield* GCP.Compute.Network("Producer", {
122 networkName: created.producer.networkName,
123 autoCreateSubnetworks: false,
124 description: "intercept producer vpc",
125 });
126 const consumer = yield* GCP.Compute.Network("Consumer", {
127 networkName: created.consumer.networkName,
128 autoCreateSubnetworks: false,
129 description: "intercept consumer vpc",
130 });
131 const collectors =
132 yield* GCP.NetworkSecurity.InterceptDeploymentGroup("Inspect", {
133 interceptDeploymentGroupId:
134 created.collectors.interceptDeploymentGroupId,
135 network: producer.selfLink.as<string>(),
136 description: "collectors",
137 });
138 const endpoints = yield* GCP.NetworkSecurity.InterceptEndpointGroup(
139 "Front",
140 {
141 interceptEndpointGroupId:
142 created.endpoints.interceptEndpointGroupId,
143 interceptDeploymentGroup: collectors.name,
144 },
145 );
146 const association =
147 yield* GCP.NetworkSecurity.InterceptEndpointGroupAssociation(
148 "Link",
149 {
150 interceptEndpointGroupAssociationId:
151 created.association.interceptEndpointGroupAssociationId,
152 interceptEndpointGroup: endpoints.name,
153 network: consumer.selfLink.as<string>(),
154 labels: { env: "prod", role: "nsi" },
155 },
156 );
157 return { producer, consumer, collectors, endpoints, association };
158 }),
159 );
160
161 expect(updated.association.name).toEqual(created.association.name);
162 expect(updated.association.labels).toMatchObject({
163 env: "prod",
164 role: "nsi",
165 });
166
167 const refetched =
168 yield* networksecurity.getProjectsLocationsInterceptEndpointGroupAssociations(
169 {
170 name: created.association.name,
171 },
172 );
173 expect(refetched.labels?.env).toEqual("prod");
174 expect(refetched.labels?.role).toEqual("nsi");
175
176 yield* stack.destroy();
177
178 const gone = yield* waitUntilGone(created.association.name);
179 expect(gone).toEqual("gone");
180 }).pipe(logLevel, withNetworkSlots(2)),
181 {
182 tags: ["provider:gcp", "provider:gcp:networksecurity", "live"],
183 timeout: 600_000,
184 },
185);