1import { GcpEnvironment } from "@/GCP/Environment";
2import * as GCP from "@/GCP";
3import * as Test from "@/Test/Alchemy";
4import * as privateca from "@distilled.cloud/gcp/privateca_v1";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import { MinimumLogLevel } from "effect/References";
8import * as Schedule from "effect/Schedule";
9
10const { test } = Test.make({ providers: GCP.providers() });
11
12const logLevel = Effect.provideService(
13 MinimumLogLevel,
14 process.env.DEBUG ? "Debug" : "Info",
15);
16
17const location = "us-central1";
18
19const waitUntilGone = (name: string) =>
20 privateca.getProjectsLocationsCaPoolsCertificateAuthorities({ name }).pipe(
21 Effect.map((ca) =>
22 (ca.state ?? "").toUpperCase() === "DELETED"
23 ? ("gone" as const)
24 : ("found" as const),
25 ),
26 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
27 Effect.repeat({
28 schedule: Schedule.spaced("2 seconds"),
29 until: (status) => status === "gone",
30 times: 10,
31 }),
32 );
33
34test.provider(
35 "getProjectsLocationsCaPoolsCertificateAuthorities on a missing CA fails with NotFound",
36 (stack) =>
37 Effect.gen(function* () {
38 const { project } = yield* GcpEnvironment.current;
39
40 yield* stack.destroy();
41
42 const error = yield* Effect.flip(
43 privateca.getProjectsLocationsCaPoolsCertificateAuthorities({
44 name: `projects/${project}/locations/${location}/caPools/alchemy-capool-missing/certificateAuthorities/alchemy-ca-missing`,
45 }),
46 );
47 expect(error._tag).toBe("NotFound");
48
49 const page =
50 yield* privateca.listProjectsLocationsCaPoolsCertificateAuthorities({
51 parent: `projects/${project}/locations/${location}/caPools/-`,
52 pageSize: 10,
53 });
54 expect(
55 (page.certificateAuthorities ?? []).map((authority) => authority.name),
56 ).not.toContain(
57 `projects/${project}/locations/${location}/caPools/alchemy-capool-missing/certificateAuthorities/alchemy-ca-missing`,
58 );
59
60 yield* stack.destroy();
61 }).pipe(logLevel),
62 { tags: ["provider:gcp", "provider:gcp:privateca", "live"], timeout: 90_000 },
63);
64
65test.provider(
66 "create, update, and delete a certificate authority",
67 (stack) =>
68 Effect.gen(function* () {
69 yield* stack.destroy();
70
71 const created = yield* stack.deploy(
72 Effect.gen(function* () {
73 const pool = yield* GCP.PrivateCA.CaPool("Pool", {
74 location,
75 tier: "DEVOPS",
76 labels: { env: "test" },
77 });
78 return yield* GCP.PrivateCA.CertificateAuthority("Root", {
79 caPool: pool.name,
80 location,
81 type: "SELF_SIGNED",
82 desiredState: "STAGED",
83 lifetime: "315360000s",
84 keySpec: { algorithm: "EC_P256_SHA256" },
85 labels: { env: "test" },
86 config: {
87 subjectConfig: {
88 subject: {
89 organization: "Alchemy",
90 commonName: "Alchemy Test Root",
91 },
92 },
93 x509Config: {
94 caOptions: { isCa: true },
95 keyUsage: {
96 baseKeyUsage: {
97 certSign: true,
98 crlSign: true,
99 },
100 },
101 },
102 },
103 });
104 }),
105 );
106
107 expect(created.name).toContain("/certificateAuthorities/");
108 expect(created.certificateAuthorityId).toEqual(expect.any(String));
109 expect(created.location).toEqual(location);
110 expect(created.type).toEqual("SELF_SIGNED");
111 expect(created.caPool).toContain("/caPools/");
112 expect(created.labels).toMatchObject({ env: "test" });
113 expect(created.state).toEqual("STAGED");
114
115 const fetched =
116 yield* privateca.getProjectsLocationsCaPoolsCertificateAuthorities({
117 name: created.name,
118 });
119 expect(fetched.name).toEqual(created.name);
120 expect(fetched.type).toEqual("SELF_SIGNED");
121 expect(fetched.state).toEqual("STAGED");
122 expect(fetched.labels?.env).toEqual("test");
123
124 const updated = yield* stack.deploy(
125 Effect.gen(function* () {
126 const pool = yield* GCP.PrivateCA.CaPool("Pool", {
127 location,
128 caPoolId: created.caPool.split("/").pop(),
129 tier: "DEVOPS",
130 labels: { env: "test" },
131 });
132 return yield* GCP.PrivateCA.CertificateAuthority("Root", {
133 caPool: pool.name,
134 location,
135 certificateAuthorityId: created.certificateAuthorityId,
136 type: "SELF_SIGNED",
137 desiredState: "STAGED",
138 lifetime: "315360000s",
139 keySpec: { algorithm: "EC_P256_SHA256" },
140 labels: { env: "prod", role: "ca" },
141 config: {
142 subjectConfig: {
143 subject: {
144 organization: "Alchemy",
145 commonName: "Alchemy Test Root",
146 },
147 },
148 x509Config: {
149 caOptions: { isCa: true },
150 keyUsage: {
151 baseKeyUsage: {
152 certSign: true,
153 crlSign: true,
154 },
155 },
156 },
157 },
158 });
159 }),
160 );
161
162 expect(updated.name).toEqual(created.name);
163 expect(updated.labels).toMatchObject({ env: "prod", role: "ca" });
164 expect(updated.state).toEqual("STAGED");
165
166 const refetched =
167 yield* privateca.getProjectsLocationsCaPoolsCertificateAuthorities({
168 name: created.name,
169 });
170 expect(refetched.labels?.env).toEqual("prod");
171 expect(refetched.labels?.role).toEqual("ca");
172
173 yield* stack.destroy();
174
175 const gone = yield* waitUntilGone(created.name);
176 expect(gone).toEqual("gone");
177 }).pipe(logLevel),
178 {
179 tags: ["provider:gcp", "provider:gcp:privateca", "live"],
180 timeout: 120_000,
181 },
182);