1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as dns from "@distilled.cloud/gcp/dns_v1";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import { MinimumLogLevel } from "effect/References";
7import * as Schedule from "effect/Schedule";
8import { withNetworkSlot } from "../networkQuota.ts";
9
10const { test } = Test.make({ providers: GCP.providers() });
11
12const logLevel = Effect.provideService(
13 MinimumLogLevel,
14 process.env.DEBUG ? "Debug" : "Info",
15);
16
17const waitUntilPolicyGone = (project: string, responsePolicy: string) =>
18 dns.getResponsePolicies({ project, responsePolicy }).pipe(
19 Effect.as("found" as const),
20 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
21 Effect.repeat({
22 schedule: Schedule.spaced("1 second"),
23 until: (status) => status === "gone",
24 times: 10,
25 }),
26 );
27
28const waitUntilRuleGone = (
29 project: string,
30 responsePolicy: string,
31 ruleName: string,
32) =>
33 dns
34 .getResponsePolicyRules({
35 project,
36 responsePolicy,
37 responsePolicyRule: ruleName,
38 })
39 .pipe(
40 Effect.as("found" as const),
41 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
42 Effect.repeat({
43 schedule: Schedule.spaced("1 second"),
44 until: (status) => status === "gone",
45 times: 10,
46 }),
47 );
48
49const lastSegment = (url: string) => url.split("/").pop() ?? url;
50
51test.provider(
52 "create, update, replace, and delete a response policy and rule",
53 (stack) =>
54 Effect.gen(function* () {
55 yield* stack.destroy();
56
57 const created = yield* stack.deploy(
58 Effect.gen(function* () {
59 const vpc = yield* GCP.Compute.Network("Vpc", {
60 autoCreateSubnetworks: false,
61 });
62 const policy = yield* GCP.DNS.ResponsePolicy("Overrides", {
63 description: "alchemy test response policy",
64 labels: { env: "test" },
65 networks: [vpc.networkName],
66 });
67 const rule = yield* GCP.DNS.ResponsePolicyRule("Internal", {
68 responsePolicy: policy.responsePolicyName,
69 dnsName: "app.internal.example.com.",
70 localData: [{ type: "A", ttl: 300, rrdatas: ["10.0.0.10"] }],
71 });
72 return { vpc, policy, rule };
73 }),
74 );
75
76 expect(created.policy.responsePolicyName).toEqual(expect.any(String));
77 expect(created.policy.description).toEqual(
78 "alchemy test response policy",
79 );
80 expect(created.policy.labels).toMatchObject({ env: "test" });
81 expect(created.policy.networks.map(lastSegment)).toContain(
82 created.vpc.networkName,
83 );
84 expect(created.rule.responsePolicy).toEqual(
85 created.policy.responsePolicyName,
86 );
87 expect(created.rule.dnsName).toEqual("app.internal.example.com.");
88 expect(created.rule.localData).toEqual([
89 {
90 name: "app.internal.example.com.",
91 type: "A",
92 ttl: 300,
93 rrdatas: ["10.0.0.10"],
94 },
95 ]);
96
97 const fetched = yield* dns.getResponsePolicies({
98 project: created.policy.project,
99 responsePolicy: created.policy.responsePolicyName,
100 });
101 expect(fetched.responsePolicyName).toEqual(
102 created.policy.responsePolicyName,
103 );
104 expect(fetched.description).toEqual("alchemy test response policy");
105 expect(fetched.labels?.env).toEqual("test");
106
107 const fetchedRule = yield* dns.getResponsePolicyRules({
108 project: created.rule.project,
109 responsePolicy: created.policy.responsePolicyName,
110 responsePolicyRule: created.rule.ruleName,
111 });
112 expect(fetchedRule.dnsName).toEqual("app.internal.example.com.");
113 expect(fetchedRule.localData?.localDatas?.[0]?.rrdatas).toEqual([
114 "10.0.0.10",
115 ]);
116
117 const updated = yield* stack.deploy(
118 Effect.gen(function* () {
119 const vpc = yield* GCP.Compute.Network("Vpc", {
120 networkName: created.vpc.networkName,
121 autoCreateSubnetworks: false,
122 });
123 const policy = yield* GCP.DNS.ResponsePolicy("Overrides", {
124 responsePolicyName: created.policy.responsePolicyName,
125 description: "updated alchemy test response policy",
126 labels: { env: "prod", role: "dns" },
127 networks: [vpc.networkName],
128 });
129 const rule = yield* GCP.DNS.ResponsePolicyRule("Internal", {
130 responsePolicy: policy.responsePolicyName,
131 ruleName: created.rule.ruleName,
132 dnsName: "app.internal.example.com.",
133 localData: [{ type: "A", ttl: 60, rrdatas: ["10.0.0.20"] }],
134 });
135 return { vpc, policy, rule };
136 }),
137 );
138
139 expect(updated.policy.responsePolicyName).toEqual(
140 created.policy.responsePolicyName,
141 );
142 expect(updated.policy.description).toEqual(
143 "updated alchemy test response policy",
144 );
145 expect(updated.policy.labels).toMatchObject({
146 env: "prod",
147 role: "dns",
148 });
149 expect(updated.rule.localData).toEqual([
150 {
151 name: "app.internal.example.com.",
152 type: "A",
153 ttl: 60,
154 rrdatas: ["10.0.0.20"],
155 },
156 ]);
157
158 const fetchedUpdate = yield* dns.getResponsePolicies({
159 project: created.policy.project,
160 responsePolicy: created.policy.responsePolicyName,
161 });
162 expect(fetchedUpdate.description).toEqual(
163 "updated alchemy test response policy",
164 );
165 expect(fetchedUpdate.labels?.env).toEqual("prod");
166 expect(fetchedUpdate.labels?.role).toEqual("dns");
167
168 const fetchedUpdatedRule = yield* dns.getResponsePolicyRules({
169 project: created.rule.project,
170 responsePolicy: created.policy.responsePolicyName,
171 responsePolicyRule: created.rule.ruleName,
172 });
173 expect(fetchedUpdatedRule.localData?.localDatas?.[0]?.ttl).toEqual(60);
174 expect(fetchedUpdatedRule.localData?.localDatas?.[0]?.rrdatas).toEqual([
175 "10.0.0.20",
176 ]);
177
178 const replacedRuleName = `r${created.rule.ruleName}`.slice(0, 63);
179 const replaced = yield* stack.deploy(
180 Effect.gen(function* () {
181 const vpc = yield* GCP.Compute.Network("Vpc", {
182 networkName: created.vpc.networkName,
183 autoCreateSubnetworks: false,
184 });
185 const policy = yield* GCP.DNS.ResponsePolicy("Overrides", {
186 responsePolicyName: created.policy.responsePolicyName,
187 description: "updated alchemy test response policy",
188 labels: { env: "prod", role: "dns" },
189 networks: [vpc.networkName],
190 });
191 const rule = yield* GCP.DNS.ResponsePolicyRule("Internal", {
192 responsePolicy: policy.responsePolicyName,
193 ruleName: replacedRuleName,
194 dnsName: "api.internal.example.com.",
195 localData: [{ type: "A", ttl: 120, rrdatas: ["10.0.0.30"] }],
196 });
197 return { vpc, policy, rule };
198 }),
199 );
200
201 expect(replaced.rule.ruleName).toEqual(replacedRuleName);
202 expect(replaced.rule.dnsName).toEqual("api.internal.example.com.");
203 expect(replaced.rule.localData[0]?.rrdatas).toEqual(["10.0.0.30"]);
204
205 const previousRuleGone = yield* waitUntilRuleGone(
206 created.rule.project,
207 created.policy.responsePolicyName,
208 created.rule.ruleName,
209 );
210 expect(previousRuleGone).toEqual("gone");
211
212 const fetchedReplacedRule = yield* dns.getResponsePolicyRules({
213 project: created.rule.project,
214 responsePolicy: created.policy.responsePolicyName,
215 responsePolicyRule: replacedRuleName,
216 });
217 expect(fetchedReplacedRule.dnsName).toEqual("api.internal.example.com.");
218
219 const replacedPolicyName = `r${created.policy.responsePolicyName}`.slice(
220 0,
221 63,
222 );
223 const replacedPolicy = yield* stack.deploy(
224 Effect.gen(function* () {
225 const vpc = yield* GCP.Compute.Network("Vpc", {
226 networkName: created.vpc.networkName,
227 autoCreateSubnetworks: false,
228 });
229 const policy = yield* GCP.DNS.ResponsePolicy("Overrides", {
230 responsePolicyName: replacedPolicyName,
231 description: "replaced alchemy test response policy",
232 labels: { env: "prod", role: "dns" },
233 networks: [vpc.networkName],
234 });
235 const rule = yield* GCP.DNS.ResponsePolicyRule("Internal", {
236 responsePolicy: policy.responsePolicyName,
237 ruleName: replacedRuleName,
238 dnsName: "api.internal.example.com.",
239 localData: [{ type: "A", ttl: 120, rrdatas: ["10.0.0.30"] }],
240 });
241 return { vpc, policy, rule };
242 }),
243 );
244
245 expect(replacedPolicy.policy.responsePolicyName).toEqual(
246 replacedPolicyName,
247 );
248 expect(replacedPolicy.policy.description).toEqual(
249 "replaced alchemy test response policy",
250 );
251 expect(replacedPolicy.rule.responsePolicy).toEqual(replacedPolicyName);
252
253 const previousPolicyGone = yield* waitUntilPolicyGone(
254 created.policy.project,
255 created.policy.responsePolicyName,
256 );
257 expect(previousPolicyGone).toEqual("gone");
258
259 const fetchedReplacement = yield* dns.getResponsePolicies({
260 project: created.policy.project,
261 responsePolicy: replacedPolicyName,
262 });
263 expect(fetchedReplacement.responsePolicyName).toEqual(replacedPolicyName);
264 expect(fetchedReplacement.description).toEqual(
265 "replaced alchemy test response policy",
266 );
267
268 yield* stack.destroy();
269
270 const ruleGone = yield* waitUntilRuleGone(
271 created.rule.project,
272 replacedPolicyName,
273 replacedRuleName,
274 );
275 expect(ruleGone).toEqual("gone");
276
277 const gone = yield* waitUntilPolicyGone(
278 created.policy.project,
279 replacedPolicyName,
280 );
281 expect(gone).toEqual("gone");
282 }).pipe(logLevel, withNetworkSlot),
283 { tags: ["provider:gcp", "provider:gcp:dns", "live"], timeout: 120_000 },
284);