| 1 | import { createHash } from "node:crypto"; |
| 2 | import * as Effect from "effect/Effect"; |
| 3 | import { createPhysicalName } from "../../PhysicalName.ts"; |
| 4 | |
| 5 | /** KeyRing and CryptoKey ids match `[a-zA-Z0-9_-]{1,63}`. */ |
| 6 | export const MAX_KMS_ID_LENGTH = 63; |
| 7 | |
| 8 | const 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 | */ |
| 23 | export 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 | }); |