Skip to content

Instantly share code, notes, and snippets.

@sunziping2016
Last active April 3, 2024 03:31
Show Gist options
  • Select an option

  • Save sunziping2016/8f34af3b9bd9635cd2ff14ca70a55781 to your computer and use it in GitHub Desktop.

Select an option

Save sunziping2016/8f34af3b9bd9635cd2ff14ca70a55781 to your computer and use it in GitHub Desktop.
Kubenetes remote command client (i.e., kubectl exec) implementation with Node.js and WebSocket
// npm install ws
const WebSocket = require("ws");
const KUBERNETES_HOST = "127.0.0.1:443";
const KUBERNETES_NAMESPACE = "default";
const KUBERNETES_TOKEN = "CHANGEME";
const pod = "changeme";
if (!process.stdin.isTTY && !process.stdout.isTTY) {
console.error("not tty");
process.exit(1);
}
process.stdin.setRawMode(true);
const wsUrl = `wss://${KUBERNETES_HOST}/api/v1/namespaces/${KUBERNETES_NAMESPACE}/pods/${pod}/exec?command=bash&stdin=true&stdout=true&tty=true`;
// see https://github.com/kubernetes/kubernetes/blob/v1.26.5/pkg/kubelet/cri/streaming/remotecommand/websocket.go
const ws = new WebSocket(
wsUrl,
[
// see https://github.com/kubernetes/kubernetes/pull/47740
// use either the base64url.* subprotocol as following or the authorization header
"base64url.bearer.authorization.k8s.io." +
Buffer.from(KUBERNETES_TOKEN).toString("base64url"),
"v4.channel.k8s.io",
],
{
// TLS secure?
rejectUnauthorized: false,
// headers: {
// Authorization: `Bearer ${KUBERNETES_TOKEN}`,
// },
protocolVersion: 13,
}
);
ws.on("error", (err) => {
ws.close();
console.error(err);
});
let exitCode = 0;
ws.on("close", () => {
process.exit(exitCode);
});
ws.on("message", (data) => {
switch (data.at(0)) {
case 1:
process.stdout.write(data.slice(1));
break;
case 3:
const err = JSON.parse(data.slice(1));
if (err.status === "Failure") {
exitCode = 1;
if (err.reason === "NonZeroExitCode") {
const exitCause = err.details.causes.find(
(x) => (x.reason = "ExitCode")
);
if (exitCause !== null) {
exitCode = parseInt(exitCause.message, 10);
}
}
console.error(err.message);
}
ws.close();
break;
}
});
const stdinChannel = Buffer.from([0]);
const resizeChannel = Buffer.from([4]);
process.stdin.on("data", (data) => {
ws.send(Buffer.concat([stdinChannel, data]));
});
const sendSize = () => {
ws.send(
Buffer.concat([
resizeChannel,
Buffer.from(
JSON.stringify({
Width: process.stdout.columns,
Height: process.stdout.rows,
}),
"utf8"
),
])
);
};
ws.on("open", sendSize);
process.stdout.on("resize", sendSize);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment