import { readFileSync } from "fs"; // Read in-cluster service account token and CA const SA_TOKEN_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/token"; const K8S_API = process.env.KUBERNETES_SERVICE_HOST ? `https://${process.env.KUBERNETES_SERVICE_HOST}:${process.env.KUBERNETES_SERVICE_PORT}` : "https://kubernetes.default.svc"; function token() { return readFileSync(SA_TOKEN_PATH, "utf-8").trim(); } export async function k8sFetch(path: string, options: RequestInit = {}, allowStatuses: number[] = []) { const res = await fetch(`${K8S_API}${path}`, { ...options, headers: { Authorization: `Bearer ${token()}`, "Content-Type": "application/json", ...(options.headers ?? {}), }, // @ts-ignore - bun supports this tls: { rejectUnauthorized: false }, }); if (!res.ok && !allowStatuses.includes(res.status)) { const body = await res.text(); throw new Error(`k8s API ${options.method ?? "GET"} ${path} → ${res.status}: ${body}`); } return res; } export async function getSecret(namespace: string, name: string): Promise> { const res = await k8sFetch(`/api/v1/namespaces/${namespace}/secrets/${name}`); const secret = await res.json() as { data: Record }; return Object.fromEntries( Object.entries(secret.data).map(([k, v]) => [k, Buffer.from(v, "base64").toString()]) ); } export async function patchSecret(namespace: string, name: string, data: Record) { const encoded = Object.fromEntries( Object.entries(data).map(([k, v]) => [k, Buffer.from(v).toString("base64")]) ); return (await k8sFetch(`/api/v1/namespaces/${namespace}/secrets/${name}`, { method: "PATCH", headers: { "Content-Type": "application/strategic-merge-patch+json" }, body: JSON.stringify({ data: encoded }), })).json(); } export async function createSecret(namespace: string, name: string, data: Record) { const encoded = Object.fromEntries( Object.entries(data).map(([k, v]) => [k, Buffer.from(v).toString("base64")]) ); await k8sFetch(`/api/v1/namespaces/${namespace}/secrets`, { method: "POST", body: JSON.stringify({ apiVersion: "v1", kind: "Secret", metadata: { name, namespace }, data: encoded, }), }, [409]); // 409 = already exists, ignore } export async function createDockerRegistrySecret( namespace: string, name: string, opts: { server: string; username: string; password: string }, ) { const auth = Buffer.from(`${opts.username}:${opts.password}`).toString("base64"); const dockerConfig = JSON.stringify({ auths: { [opts.server]: { username: opts.username, password: opts.password, auth } }, }); const encoded = Buffer.from(dockerConfig).toString("base64"); await k8sFetch(`/api/v1/namespaces/${namespace}/secrets`, { method: "POST", body: JSON.stringify({ apiVersion: "v1", kind: "Secret", metadata: { name, namespace }, type: "kubernetes.io/dockerconfigjson", data: { ".dockerconfigjson": encoded }, }), }, [409]); } export async function createNamespace(name: string) { await k8sFetch(`/api/v1/namespaces`, { method: "POST", body: JSON.stringify({ apiVersion: "v1", kind: "Namespace", metadata: { name }, }), }, [409]); // 409 = already exists, ignore } export async function deleteNamespace(name: string) { await k8sFetch(`/api/v1/namespaces/${name}`, { method: "DELETE" }, [404]); } export async function patchConfigMap(namespace: string, name: string, data: Record) { return (await k8sFetch(`/api/v1/namespaces/${namespace}/configmaps/${name}`, { method: "PATCH", headers: { "Content-Type": "application/strategic-merge-patch+json" }, body: JSON.stringify({ data }), })).json(); } export async function getConfigMap(namespace: string, name: string): Promise> { const res = await k8sFetch(`/api/v1/namespaces/${namespace}/configmaps/${name}`); const cm = await res.json() as { data?: Record }; return cm.data ?? {}; } export async function syncArgoApp(name: string) { await k8sFetch(`/apis/argoproj.io/v1alpha1/namespaces/argocd/applications/${name}`, { method: "PATCH", headers: { "Content-Type": "application/merge-patch+json" }, body: JSON.stringify({ metadata: { annotations: { "argocd.argoproj.io/refresh": "hard" } }, operation: { sync: { prune: true } }, }), }, [404]); } export async function rolloutRestart(namespace: string, deployment: string) { return (await k8sFetch(`/apis/apps/v1/namespaces/${namespace}/deployments/${deployment}`, { method: "PATCH", headers: { "Content-Type": "application/strategic-merge-patch+json" }, body: JSON.stringify({ spec: { template: { metadata: { annotations: { "kubectl.kubernetes.io/restartedAt": new Date().toISOString() } } } }, }), })).json(); }