61 lines
1.8 KiB
TypeScript
61 lines
1.8 KiB
TypeScript
import { defineCommand } from "citty";
|
|
import { getPodContainerName, getPodForDeployment } from "../lib/shared";
|
|
import { execClient } from "../lib/k8s";
|
|
|
|
function prepareInteractiveStdin(): (() => void) | undefined {
|
|
if (!process.stdin.isTTY) return;
|
|
|
|
process.stdin.setRawMode?.(true);
|
|
process.stdin.resume();
|
|
return () => {
|
|
process.stdin.setRawMode?.(false);
|
|
process.stdin.pause();
|
|
};
|
|
}
|
|
|
|
export const exec = defineCommand({
|
|
meta: {
|
|
name: "exec",
|
|
description: "Execute a command inside a deployment pod",
|
|
},
|
|
async run({ args }) {
|
|
const [deployment, ...command] = args._;
|
|
if (!deployment) throw new Error("Deployment name is required");
|
|
if (command.length === 0) throw new Error("Command is required");
|
|
|
|
const pod = await getPodForDeployment(deployment);
|
|
if (pod.status?.phase !== "Running") {
|
|
throw new Error(
|
|
`Deployment ${deployment} has no running pod to exec into (${pod.metadata?.name ?? "unknown pod"} is ${pod.status?.phase ?? "not ready"})`,
|
|
);
|
|
}
|
|
|
|
const namespace = pod.metadata?.namespace ?? "default";
|
|
const podName = pod.metadata?.name;
|
|
if (!podName) throw new Error(`No pod name found for deployment ${deployment}`);
|
|
|
|
const restoreStdin = prepareInteractiveStdin();
|
|
|
|
try {
|
|
const socket = await execClient.exec(
|
|
namespace,
|
|
podName,
|
|
getPodContainerName(pod),
|
|
command,
|
|
process.stdout,
|
|
process.stderr,
|
|
process.stdin,
|
|
Boolean(process.stdin.isTTY),
|
|
);
|
|
|
|
await new Promise<void>((resolve, reject) => {
|
|
socket.onclose = () => resolve();
|
|
socket.onerror = (event: { error?: unknown }) =>
|
|
reject(event.error ?? new Error("Exec failed"));
|
|
});
|
|
} finally {
|
|
restoreStdin?.();
|
|
}
|
|
},
|
|
});
|