1import * as Effect from "effect/Effect";
2import { createPhysicalName } from "../../PhysicalName.ts";
3
4export const MAX_NAME_LENGTH = 63;
5
6export const lastSegment = (value: string) => {
7 const trimmed = value.replace(/\/+$/, "");
8 const parts = trimmed.split("/");
9 return parts[parts.length - 1] || trimmed;
10};
11
12export const rfc1035 = (name: string): string => {
13 let next = name
14 .toLowerCase()
15 .replace(/[^a-z0-9-]/g, "-")
16 .replace(/-+/g, "-")
17 .replace(/^-+|-+$/g, "");
18 if (!/^[a-z]/.test(next)) next = `c${next}`;
19 next = next.slice(0, MAX_NAME_LENGTH).replace(/-+$/g, "");
20 if (next.length === 0) return "workload";
21 if (!/[a-z0-9]$/.test(next)) next = `${next.slice(0, MAX_NAME_LENGTH - 1)}0`;
22 return next.slice(0, MAX_NAME_LENGTH);
23};
24
25export const toPhysicalId = (
26 id: string,
27 explicit: string | undefined,
28 existing: string | undefined,
29) =>
30 Effect.gen(function* () {
31 if (explicit !== undefined) return explicit;
32 if (existing !== undefined) return existing;
33 return rfc1035(
34 yield* createPhysicalName({
35 id,
36 maxLength: MAX_NAME_LENGTH,
37 lowercase: true,
38 }),
39 );
40 });
41
42export type ParsedWorkloadName = {
43 project: string;
44 location: string;
45 environmentId: string;
46 configMapId: string | undefined;
47 secretId: string | undefined;
48};
49
50export const parseWorkloadName = (name: string): ParsedWorkloadName => {
51 const parts = name.split("/").filter((part) => part.length > 0);
52 const get = (key: string) => {
53 const index = parts.lastIndexOf(key);
54 return index >= 0 ? parts[index + 1] : undefined;
55 };
56 return {
57 project: get("projects") ?? "",
58 // Environment names are always fully qualified; no regional default.
59 location: get("locations") ?? "",
60 environmentId: get("environments") ?? lastSegment(name),
61 configMapId: get("userWorkloadsConfigMaps"),
62 secretId: get("userWorkloadsSecrets"),
63 };
64};
65
66export const environmentParent = (environmentName: string) => {
67 const parsed = parseWorkloadName(environmentName);
68 return `projects/${parsed.project}/locations/${parsed.location}/environments/${parsed.environmentId}`;
69};
70
71export const mapOf = (
72 map: Record<string, string | undefined> | null | undefined,
73): Record<string, string> =>
74 Object.fromEntries(
75 Object.entries(map ?? {}).filter(
76 (entry): entry is [string, string] => entry[1] !== undefined,
77 ),
78 );
79
80export const dataKey = (
81 map: Record<string, string | undefined> | null | undefined,
82) =>
83 JSON.stringify(
84 Object.fromEntries(
85 Object.entries(mapOf(map)).sort(([left], [right]) =>
86 left.localeCompare(right),
87 ),
88 ),
89 );