1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as certificatemanager from "@distilled.cloud/gcp/certificatemanager_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 runLifecycle = !process.env.FAST;
18
19const location = "us-central1";
20
21const waitUntilGone = (name: string) =>
22 certificatemanager
23 .getProjectsLocationsCertificateIssuanceConfigs({ name })
24 .pipe(
25 Effect.as("found" as const),
26 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
27 Effect.repeat({
28 schedule: Schedule.spaced("1 second"),
29 until: (status) => status === "gone",
30 times: 10,
31 }),
32 );
33
34test.provider(
35 "getProjectsLocationsCertificateIssuanceConfigs on a missing config fails with NotFound",
36 (stack) =>
37 Effect.gen(function* () {
38 const { project } = yield* GcpEnvironment.current;
39 yield* stack.destroy();
40
41 const error = yield* Effect.flip(
42 certificatemanager.getProjectsLocationsCertificateIssuanceConfigs({
43 name: `projects/${project}/locations/${location}/certificateIssuanceConfigs/alchemy-issuance-missing`,
44 }),
45 );
46 expect(error._tag).toBe("NotFound");
47
48 yield* stack.destroy();
49 }).pipe(logLevel),
50 {
51 tags: ["provider:gcp", "provider:gcp:certificatemanager", "live"],
52 timeout: 90_000,
53 },
54);
55
56test.provider.skipIf(!runLifecycle)(
57 "create, update, and delete a certificate issuance config",
58 (stack) =>
59 Effect.gen(function* () {
60 yield* stack.destroy();
61
62 const created = yield* stack.deploy(
63 Effect.gen(function* () {
64 const pool = yield* GCP.PrivateCA.CaPool("IssuancePool", {
65 location,
66 tier: "DEVOPS",
67 labels: { env: "test" },
68 });
69 const ca = yield* GCP.PrivateCA.CertificateAuthority("IssuanceCa", {
70 caPool: pool.name,
71 location,
72 type: "SELF_SIGNED",
73 desiredState: "ENABLED",
74 lifetime: "315360000s",
75 keySpec: { algorithm: "EC_P256_SHA256" },
76 config: {
77 subjectConfig: {
78 subject: {
79 organization: "Alchemy",
80 commonName: "Alchemy Issuance Test Root",
81 },
82 },
83 x509Config: {
84 caOptions: { isCa: true },
85 keyUsage: {
86 baseKeyUsage: {
87 certSign: true,
88 crlSign: true,
89 },
90 },
91 },
92 },
93 });
94 return yield* GCP.CertificateManager.CertificateIssuanceConfig(
95 "WorkloadTls",
96 {
97 location,
98 description: "issuance a",
99 labels: { env: "test", ca: ca.certificateAuthorityId },
100 lifetime: "2592000s",
101 rotationWindowPercentage: 66,
102 keyAlgorithm: "ECDSA_P256",
103 certificateAuthorityConfig: {
104 certificateAuthorityServiceConfig: {
105 caPool: pool.name,
106 },
107 },
108 },
109 );
110 }),
111 );
112
113 expect(created.name).toContain("/certificateIssuanceConfigs/");
114 expect(created.certificateIssuanceConfigId).toEqual(expect.any(String));
115 expect(created.location).toEqual(location);
116 expect(created.description).toEqual("issuance a");
117 expect(created.labels).toMatchObject({ env: "test" });
118 expect(created.lifetime).toEqual("2592000s");
119 expect(created.rotationWindowPercentage).toEqual(66);
120 expect(created.keyAlgorithm).toEqual("ECDSA_P256");
121 expect(created.caPool).toContain("/caPools/");
122
123 const fetched =
124 yield* certificatemanager.getProjectsLocationsCertificateIssuanceConfigs(
125 {
126 name: created.name,
127 },
128 );
129 expect(fetched.name).toEqual(created.name);
130 expect(fetched.description).toEqual("issuance a");
131 expect(fetched.labels?.env).toEqual("test");
132 expect(fetched.lifetime).toEqual("2592000s");
133 expect(fetched.rotationWindowPercentage).toEqual(66);
134 expect(fetched.keyAlgorithm).toEqual("ECDSA_P256");
135 expect(
136 fetched.certificateAuthorityConfig?.certificateAuthorityServiceConfig
137 ?.caPool,
138 ).toEqual(created.caPool);
139 expect(
140 Object.keys(fetched.labels ?? {}).some((key) =>
141 key.startsWith("alchemy-"),
142 ),
143 ).toEqual(true);
144
145 const updated = yield* stack.deploy(
146 Effect.gen(function* () {
147 const pool = yield* GCP.PrivateCA.CaPool("IssuancePool", {
148 location,
149 caPoolId: created.caPool.split("/").pop(),
150 tier: "DEVOPS",
151 labels: { env: "test" },
152 });
153 const ca = yield* GCP.PrivateCA.CertificateAuthority("IssuanceCa", {
154 caPool: pool.name,
155 location,
156 type: "SELF_SIGNED",
157 desiredState: "ENABLED",
158 lifetime: "315360000s",
159 keySpec: { algorithm: "EC_P256_SHA256" },
160 config: {
161 subjectConfig: {
162 subject: {
163 organization: "Alchemy",
164 commonName: "Alchemy Issuance Test Root",
165 },
166 },
167 x509Config: {
168 caOptions: { isCa: true },
169 keyUsage: {
170 baseKeyUsage: {
171 certSign: true,
172 crlSign: true,
173 },
174 },
175 },
176 },
177 });
178 return yield* GCP.CertificateManager.CertificateIssuanceConfig(
179 "WorkloadTls",
180 {
181 certificateIssuanceConfigId: created.certificateIssuanceConfigId,
182 location,
183 description: "issuance b",
184 labels: {
185 env: "prod",
186 role: "tls",
187 ca: ca.certificateAuthorityId,
188 },
189 lifetime: "2592000s",
190 rotationWindowPercentage: 66,
191 keyAlgorithm: "ECDSA_P256",
192 certificateAuthorityConfig: {
193 certificateAuthorityServiceConfig: {
194 caPool: pool.name,
195 },
196 },
197 },
198 );
199 }),
200 );
201
202 expect(updated.name).toEqual(created.name);
203 expect(updated.description).toEqual("issuance b");
204 expect(updated.labels).toMatchObject({ env: "prod", role: "tls" });
205 expect(updated.lifetime).toEqual("2592000s");
206 expect(updated.keyAlgorithm).toEqual("ECDSA_P256");
207 expect(updated.caPool).toEqual(created.caPool);
208
209 const refetched =
210 yield* certificatemanager.getProjectsLocationsCertificateIssuanceConfigs(
211 {
212 name: created.name,
213 },
214 );
215 expect(refetched.description).toEqual("issuance b");
216 expect(refetched.labels?.env).toEqual("prod");
217 expect(refetched.labels?.role).toEqual("tls");
218 expect(refetched.lifetime).toEqual("2592000s");
219 expect(refetched.keyAlgorithm).toEqual("ECDSA_P256");
220
221 yield* stack.destroy();
222
223 const gone = yield* waitUntilGone(created.name);
224 expect(gone).toEqual("gone");
225 }).pipe(logLevel),
226 {
227 tags: ["provider:gcp", "provider:gcp:certificatemanager", "live"],
228 timeout: 120_000,
229 },
230);