Files
kuber/lib/shared.ts
T
2026-09-27 10:40:50 +00:00

157 lines
4.0 KiB
TypeScript

import type { V1Deployment, V1Pod } from "@kubernetes/client-node";
import { LABELS } from "../const";
import { ctx } from "./context";
import { apps, core, objectApi } from "./k8s";
import { waitForRollout } from "./rollout";
const ManagedBySelector = `app.kubernetes.io/managed-by=${LABELS["app.kubernetes.io/managed-by"]}`;
export function getProject() {
return ctx().project;
}
export async function listDeployments(labelSelector?: string) {
const { items } = await apps.listNamespacedDeployment({
namespace: getProject(),
labelSelector,
});
return items;
}
export function listManagedDeployments() {
return listDeployments(ManagedBySelector);
}
export async function getDeployment(name: string): Promise<V1Deployment> {
const deployment = await apps.readNamespacedDeployment({
namespace: getProject(),
name,
});
return deployment;
}
export async function listPodsForDeployment(name: string): Promise<V1Pod[]> {
const { items } = await core.listNamespacedPod({
namespace: getProject(),
labelSelector: `app=${name}`,
});
return items;
}
export async function getPodForDeployment(name: string): Promise<V1Pod> {
const pods = await listPodsForDeployment(name);
const runningPod = pods.find((pod) => pod.status?.phase === "Running");
if (runningPod) return runningPod;
const pod = pods[0];
if (!pod) throw new Error(`No pod found for deployment ${name}`);
return pod;
}
export function getPodContainerName(pod: V1Pod): string {
const container = pod.spec?.containers[0]?.name;
if (!container)
throw new Error(`No container found in pod ${pod.metadata?.name}`);
return container;
}
export async function scaleDeployment(name: string, replicas: number) {
return objectApi.patch({
apiVersion: "apps/v1",
kind: "Deployment",
metadata: {
name,
namespace: getProject(),
},
spec: {
replicas,
},
});
}
export async function restartDeployment(name: string) {
return objectApi.patch({
apiVersion: "apps/v1",
kind: "Deployment",
metadata: {
name,
namespace: getProject(),
},
spec: {
template: {
metadata: {
annotations: {
"kubectl.kubernetes.io/restartedAt": new Date().toISOString(),
},
},
},
},
});
}
function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export async function waitForDeploymentRollout(
name: string,
timeoutMs = 300000,
) {
return waitForRollout(
name,
Date.now() + timeoutMs,
() => getDeployment(name),
async () =>
(await apps.listNamespacedReplicaSet({ namespace: getProject() })).items,
async () =>
(await core.listNamespacedPod({ namespace: getProject() })).items,
);
}
export function throwWhen<T, Narrowed extends T>(
condition: (arg: T) => arg is Narrowed,
message?: string,
): (arg: T) => Exclude<T, Narrowed>;
export function throwWhen<T>(
condition: (arg: T) => unknown,
message?: string,
): (arg: T) => T;
export function throwWhen<T>(condition: (arg: T) => unknown, message?: string) {
return (arg: T) => {
if (condition(arg)) throw new Error(message ?? "Condition matched");
return arg;
};
}
export function deepMerge(target: any, source: any): any {
if (
typeof target !== "object" ||
target === null ||
typeof source !== "object" ||
source === null
) {
return source;
}
const result = Array.isArray(target) ? [...target] : { ...target };
for (const key of Object.keys(source)) {
const targetValue = result[key];
const sourceValue = source[key];
if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
result[key] = [...targetValue, ...sourceValue]; // or use a custom strategy
} else if (
targetValue &&
typeof targetValue === "object" &&
sourceValue &&
typeof sourceValue === "object"
) {
result[key] = deepMerge(targetValue, sourceValue);
} else {
result[key] = sourceValue;
}
}
return result;
}