1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as networksecurity from "@distilled.cloud/gcp/networksecurity_v1";
4import * as resourcemanager from "@distilled.cloud/gcp/cloudresourcemanager_v3";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import { MinimumLogLevel } from "effect/References";
8import * as Schedule from "effect/Schedule";
9import { GcpEnvironment } from "@/GCP/Environment";
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.getOrganizationsLocationsAddressGroups({ name }).pipe(
20 Effect.as("found" as const),
21 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
22 Effect.repeat({
23 schedule: Schedule.spaced("1 second"),
24 until: (status) => status === "gone",
25 times: 10,
26 }),
27 );
28
29test.provider(
30 "getOrganizationsLocationsAddressGroups on a missing group fails with NotFound (Forbidden without org IAM)",
31 (stack) =>
32 Effect.gen(function* () {
33 const { project } = yield* GcpEnvironment.current;
34 yield* stack.destroy();
35
36 const resource = yield* resourcemanager.getProjects({
37 name: `projects/${project}`,
38 });
39 const parent = resource.parent ?? "organizations/0";
40 const organization = parent.startsWith("organizations/")
41 ? parent.slice("organizations/".length)
42 : "0";
43 const error = yield* Effect.flip(
44 networksecurity.getOrganizationsLocationsAddressGroups({
45 name: `organizations/${organization}/locations/global/addressGroups/alchemy-missing-address-group`,
46 }),
47 );
48 expect(error._tag).toEqual(
49 process.env.GCP_TEST_ORG_NETWORKSECURITY ? "NotFound" : "Forbidden",
50 );
51
52 yield* stack.destroy();
53 }).pipe(logLevel),
54 {
55 tags: ["provider:gcp", "provider:gcp:networksecurity", "live"],
56 timeout: 90_000,
57 },
58);
59
60// The testing credentials lack org-level Network Security IAM (Forbidden:
61// "Permission 'networksecurity.….get' denied on 'organizations/…'"). Set
62// GCP_TEST_ORG_NETWORKSECURITY=1 when they administer the organization.
63test.provider.skipIf(!!process.env.GCP_TEST_ORG_NETWORKSECURITY)(
64 "createOrganizationsLocationsAddressGroups without org IAM fails with Forbidden",
65 (stack) =>
66 Effect.gen(function* () {
67 const { project } = yield* GcpEnvironment.current;
68 yield* stack.destroy();
69
70 const resource = yield* resourcemanager.getProjects({
71 name: `projects/${project}`,
72 });
73 const parent = resource.parent ?? "organizations/0";
74 const organization = parent.startsWith("organizations/")
75 ? parent.slice("organizations/".length)
76 : parent.startsWith("folders/")
77 ? "0"
78 : parent;
79 const error = yield* Effect.flip(
80 networksecurity.createOrganizationsLocationsAddressGroups({
81 parent: `organizations/${organization}/locations/global`,
82 addressGroupId: "alchemy-org-address-group-probe",
83 body: {
84 type: "IPV4",
85 capacity: 100,
86 items: ["10.0.0.1"],
87 },
88 }),
89 );
90 expect(error._tag).toBe("Forbidden");
91
92 yield* stack.destroy();
93 }).pipe(logLevel),
94 {
95 tags: ["provider:gcp", "provider:gcp:networksecurity", "live"],
96 timeout: 90_000,
97 },
98);
99
100test.provider.skipIf(
101 !!process.env.FAST || !process.env.GCP_TEST_ORG_NETWORKSECURITY,
102)(
103 "create, update, and delete an organization address group",
104 (stack) =>
105 Effect.gen(function* () {
106 yield* stack.destroy();
107
108 const created = yield* stack.deploy(
109 Effect.gen(function* () {
110 return yield* GCP.NetworkSecurity.OrganizationsAddressGroup(
111 "OrgAllowlist",
112 {
113 type: "IPV4",
114 capacity: 100,
115 items: ["10.0.0.1"],
116 description: "org address group a",
117 labels: { env: "test" },
118 },
119 );
120 }),
121 );
122
123 expect(created.name).toContain("/addressGroups/");
124 expect(created.name).toContain("organizations/");
125 expect(created.addressGroupId).toEqual(expect.any(String));
126 expect(created.location).toEqual("global");
127 expect(created.items).toEqual(["10.0.0.1"]);
128 expect(created.labels).toMatchObject({ env: "test" });
129
130 const fetched =
131 yield* networksecurity.getOrganizationsLocationsAddressGroups({
132 name: created.name,
133 });
134 expect(fetched.name).toEqual(created.name);
135 expect(fetched.labels?.env).toEqual("test");
136
137 const updated = yield* stack.deploy(
138 Effect.gen(function* () {
139 return yield* GCP.NetworkSecurity.OrganizationsAddressGroup(
140 "OrgAllowlist",
141 {
142 addressGroupId: created.addressGroupId,
143 organization: created.organization,
144 type: "IPV4",
145 capacity: 100,
146 items: ["10.0.0.1", "10.1.0.0/24"],
147 description: "org address group b",
148 labels: { env: "prod", role: "allowlist" },
149 },
150 );
151 }),
152 );
153
154 expect(updated.name).toEqual(created.name);
155 expect(updated.description).toEqual("org address group b");
156 expect(updated.labels).toMatchObject({ env: "prod", role: "allowlist" });
157
158 yield* stack.destroy();
159
160 const gone = yield* waitUntilGone(created.name);
161 expect(gone).toEqual("gone");
162 }).pipe(logLevel),
163 {
164 tags: ["provider:gcp", "provider:gcp:networksecurity", "live"],
165 timeout: 90_000,
166 },
167);