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";
9
10const { test } = Test.make({ providers: GCP.providers() });
11
12const logLevel = Effect.provideService(
13 MinimumLogLevel,
14 process.env.DEBUG ? "Debug" : "Info",
15);
16
17const missingNameOf = (project: string) =>
18 `projects/${project}/locations/us-central1/authzPolicies/alchemy-missing-authz-policy`;
19
20const waitUntilGone = (name: string) =>
21 networksecurity.getProjectsLocationsAuthzPolicies({ name }).pipe(
22 Effect.as("found" as const),
23 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
24 Effect.repeat({
25 schedule: Schedule.spaced("1 second"),
26 until: (status) => status === "gone",
27 times: 10,
28 }),
29 );
30
31test.provider(
32 "getProjectsLocationsAuthzPolicies on a missing policy fails with NotFound",
33 (stack) =>
34 Effect.gen(function* () {
35 const { project } = yield* GcpEnvironment.current;
36 const missingName = missingNameOf(project);
37 yield* stack.destroy();
38
39 const error = yield* Effect.flip(
40 networksecurity.getProjectsLocationsAuthzPolicies({
41 name: missingName,
42 }),
43 );
44 expect(error._tag).toBe("NotFound");
45
46 yield* stack.destroy();
47 }).pipe(logLevel),
48 {
49 tags: ["provider:gcp", "provider:gcp:networksecurity", "live"],
50 timeout: 90_000,
51 },
52);
53
54test.provider(
55 "createProjectsLocationsAuthzPolicies without a forwarding rule fails with BadRequest",
56 (stack) =>
57 Effect.gen(function* () {
58 const { project } = yield* GcpEnvironment.current;
59 yield* stack.destroy();
60
61 const forwardingRule = `//compute.googleapis.com/projects/${project}/regions/us-central1/forwardingRules/alchemy-missing-fr`;
62 const error = yield* Effect.flip(
63 networksecurity.createProjectsLocationsAuthzPolicies({
64 parent: `projects/${project}/locations/us-central1`,
65 authzPolicyId: "alchemy-missing-authz-policy",
66 body: {
67 action: "ALLOW",
68 target: {
69 loadBalancingScheme: "INTERNAL_MANAGED",
70 resources: [forwardingRule],
71 },
72 httpRules: [
73 {
74 to: {
75 operations: [
76 { methods: ["GET"], paths: [{ prefix: "/admin" }] },
77 ],
78 },
79 },
80 ],
81 },
82 }),
83 );
84 expect(error._tag).toBe("BadRequest");
85
86 yield* stack.destroy();
87 }).pipe(logLevel),
88 {
89 tags: ["provider:gcp", "provider:gcp:networksecurity", "live"],
90 timeout: 90_000,
91 },
92);
93
94// Needs an existing application load balancer forwarding rule; set
95// GCP_TEST_AUTHZ_POLICY_TARGET to its resource name.
96test.provider.skipIf(!process.env.GCP_TEST_AUTHZ_POLICY_TARGET)(
97 "create, update, and delete an authz policy",
98 (stack) =>
99 Effect.gen(function* () {
100 yield* stack.destroy();
101
102 const forwardingRule = process.env.GCP_TEST_AUTHZ_POLICY_TARGET!;
103
104 const created = yield* stack.deploy(
105 Effect.gen(function* () {
106 return yield* GCP.NetworkSecurity.AuthzPolicy("AllowAdmin", {
107 location: "us-central1",
108 action: "ALLOW",
109 target: {
110 loadBalancingScheme: "INTERNAL_MANAGED",
111 resources: [forwardingRule],
112 },
113 httpRules: [
114 {
115 to: {
116 operations: [
117 { methods: ["GET"], paths: [{ prefix: "/admin" }] },
118 ],
119 },
120 },
121 ],
122 description: "authz a",
123 labels: { env: "test" },
124 });
125 }),
126 );
127
128 expect(created.name).toContain("/authzPolicies/");
129 expect(created.location).toEqual("us-central1");
130 expect(created.action).toEqual("ALLOW");
131 expect(created.labels).toMatchObject({ env: "test" });
132
133 const fetched = yield* networksecurity.getProjectsLocationsAuthzPolicies({
134 name: created.name,
135 });
136 expect(fetched.name).toEqual(created.name);
137 expect(fetched.labels?.env).toEqual("test");
138
139 const updated = yield* stack.deploy(
140 Effect.gen(function* () {
141 return yield* GCP.NetworkSecurity.AuthzPolicy("AllowAdmin", {
142 authzPolicyId: created.authzPolicyId,
143 location: "us-central1",
144 action: "ALLOW",
145 target: created.target ?? {
146 loadBalancingScheme: "INTERNAL_MANAGED",
147 resources: [forwardingRule],
148 },
149 httpRules: [
150 {
151 to: {
152 operations: [
153 { methods: ["GET"], paths: [{ prefix: "/admin" }] },
154 ],
155 },
156 },
157 ],
158 description: "authz b",
159 labels: { env: "prod", role: "authz" },
160 });
161 }),
162 );
163
164 expect(updated.name).toEqual(created.name);
165 expect(updated.description).toEqual("authz b");
166 expect(updated.labels).toMatchObject({ env: "prod", role: "authz" });
167
168 yield* stack.destroy();
169
170 const gone = yield* waitUntilGone(created.name);
171 expect(gone).toEqual("gone");
172 }).pipe(logLevel),
173 {
174 tags: ["provider:gcp", "provider:gcp:networksecurity", "live"],
175 timeout: 90_000,
176 },
177);