1import { createHash } from "node:crypto";
2import * as Effect from "effect/Effect";
3import { createPhysicalName } from "../../PhysicalName.ts";
4
5/** KeyRing and CryptoKey ids match `[a-zA-Z0-9_-]{1,63}`. */
6export const MAX_KMS_ID_LENGTH = 63;
7
8const VARIANT_LENGTH = 8;
9
10/**
11 * Deterministic default id for a KMS key ring or key:
12 * `{stack}-{logicalId}-{stage}`, with no per-create instance suffix.
13 *
14 * Cloud KMS never deletes key rings, and a deleted key's name is retired
15 * forever, so an instance-suffixed default leaks a new ring/key on every
16 * destroy/redeploy cycle. A stable name lets the next deploy of the same
17 * logical resource reclaim what the previous one left behind.
18 *
19 * `variant` (e.g. the key's immutable settings) appends a short hash so a
20 * replacement triggered by a change to those settings gets a distinct name
21 * and never collides with the generation it replaces.
22 */
23export const deterministicKmsId = (id: string, variant?: unknown) =>
24 Effect.gen(function* () {
25 const suffix =
26 variant === undefined
27 ? ""
28 : `-${yield* Effect.sync(() =>
29 createHash("sha256")
30 .update(JSON.stringify(variant))
31 .digest("hex")
32 .slice(0, VARIANT_LENGTH),
33 )}`;
34 const base = yield* createPhysicalName({
35 id,
36 instanceId: "",
37 suffixLength: 0,
38 maxLength: MAX_KMS_ID_LENGTH - suffix.length,
39 lowercase: true,
40 });
41 return `${base.replace(/-+$/, "")}${suffix}`;
42 });