From 65abd04ee763895b40d16194a51be3e72b1736af Mon Sep 17 00:00:00 2001 From: biersoeckli Date: Wed, 27 Nov 2024 16:59:44 +0000 Subject: [PATCH] added first version of terminal backend --- bun.lockb | Bin 293434 -> 294204 bytes package.json | 3 + src/frontend/sockets/sockets.ts | 3 +- src/server/adapter/kubernetes-api.adapter.ts | 70 ---------- src/server/services/terminal.service.ts | 131 ++++++++++++++++++ src/shared/model/terminal-setup-info.model.ts | 9 ++ src/shared/utils/stream.utils.ts | 12 ++ src/socket-io.server.ts | 5 +- src/websocket.server.ts | 31 +++++ 9 files changed, 190 insertions(+), 74 deletions(-) create mode 100644 src/server/services/terminal.service.ts create mode 100644 src/shared/model/terminal-setup-info.model.ts create mode 100644 src/shared/utils/stream.utils.ts create mode 100644 src/websocket.server.ts diff --git a/bun.lockb b/bun.lockb index 6134892da8cce1eb48c0649154a0940ea83a51f9..8ec2709a21941a9d28cb5e082de21b5b7a45e74c 100755 GIT binary patch delta 57536 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 B3SIyJ delta 57383 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

~Gp}{7yjczPFk+&xIIHS3jU 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 diff --git a/package.json b/package.json index 7d3d2b9..1b0e42c 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,8 @@ "@tanstack/react-table": "^8.20.5", "@types/bcrypt": "^5.0.2", "@types/qrcode": "^1.5.5", + "@types/ws": "^8.5.13", + "@xterm/xterm": "^5.5.0", "bcrypt": "^5.1.1", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", @@ -65,6 +67,7 @@ "ts-node": "^10.9.2", "typedi": "^0.10.0", "vaul": "^1.1.0", + "ws": "^8.18.0", "zod": "^3.23.8", "zustand": "^5.0.1" }, diff --git a/src/frontend/sockets/sockets.ts b/src/frontend/sockets/sockets.ts index 628bd5f..b0ed19e 100644 --- a/src/frontend/sockets/sockets.ts +++ b/src/frontend/sockets/sockets.ts @@ -4,5 +4,4 @@ import { Manager } from "socket.io-client"; const manager = new Manager(); -export const podLogsSocket = manager.socket("/pod-logs"); -//export const deploymentStatusSocket = manager.socket("/deployment-status"); \ No newline at end of file +export const podTerminalSocket = manager.socket("/pod-terminal"); \ No newline at end of file diff --git a/src/server/adapter/kubernetes-api.adapter.ts b/src/server/adapter/kubernetes-api.adapter.ts index e939c01..8587dbf 100644 --- a/src/server/adapter/kubernetes-api.adapter.ts +++ b/src/server/adapter/kubernetes-api.adapter.ts @@ -1,74 +1,4 @@ import * as k8s from '@kubernetes/client-node'; -/* -const getKubeConfig = () => { - const kc = new k8s.KubeConfig(); - if (process.env.NODE_ENV === 'production') { - kc.loadFromCluster(); - } else { - kc.loadFromFile('/workspace/kube-config.config'); - } - return kc; -} - -const getK8sCoreApiClient = () => { - const kc = getKubeConfig() - const k8sCoreClient = kc.makeApiClient(k8s.CoreV1Api); - return k8sCoreClient; -} -const k8sCoreClient = globalThis.k8sCoreGlobal ?? getK8sCoreApiClient() -if (process.env.NODE_ENV !== 'production') globalThis.k8sCoreGlobal = k8sCoreClient - -const getK8sAppsApiClient = () => { - const kc = getKubeConfig() - const k8sCoreClient = kc.makeApiClient(k8s.AppsV1Api); - return k8sCoreClient; -} -const k8sAppsClient = globalThis.k8sAppsGlobal ?? getK8sAppsApiClient() -if (process.env.NODE_ENV !== 'production') globalThis.k8sAppsGlobal = k8sAppsClient - -const getK8sBatchApiClient = () => { - const kc = getKubeConfig() - const k8sJobClient = kc.makeApiClient(k8s.BatchV1Api); - return k8sJobClient; -} -const k8sJobClient = globalThis.k8sJobGlobal ?? getK8sBatchApiClient() -if (process.env.NODE_ENV !== 'production') globalThis.k8sJobGlobal = k8sJobClient - - -const getK8sLogApiClient = () => { - const kc = getKubeConfig() - const logClient = new k8s.Log(kc) - return logClient; -} -const k8sLogClient = globalThis.k8sLogGlobal ?? getK8sLogApiClient() -if (process.env.NODE_ENV !== 'production') globalThis.k8sLogGlobal = k8sLogClient - -const getK8sCustomObjectsApiClient = () => { - const kc = getKubeConfig() - const client = kc.makeApiClient(k8s.CustomObjectsApi); - return client; -} -const k8sCustomObjectsClient = globalThis.k8sCustomObjectsGlobal ?? getK8sCustomObjectsApiClient() -if (process.env.NODE_ENV !== 'production') globalThis.k8sCustomObjectsGlobal = k8sCustomObjectsClient - -const getK8sNetworkApiClient = () => { - const kc = getKubeConfig() - const networkClient = kc.makeApiClient(k8s.NetworkingV1Api); - return networkClient; -} -const k8sNetworkClient = globalThis.k8sNetworkGlobal ?? getK8sNetworkApiClient() -if (process.env.NODE_ENV !== 'production') globalThis.k8sNetworkGlobal = k8sNetworkClient - -declare const globalThis: { - k8sCoreGlobal: ReturnType; - k8sAppsGlobal: ReturnType; - k8sJobGlobal: ReturnType; - k8sLogGlobal: ReturnType; - k8sNetworkGlobal: ReturnType; - k8sCustomObjectsGlobal: ReturnType; -} & typeof global; - -*/ class K3sApiAdapter { diff --git a/src/server/services/terminal.service.ts b/src/server/services/terminal.service.ts new file mode 100644 index 0000000..4a6ab85 --- /dev/null +++ b/src/server/services/terminal.service.ts @@ -0,0 +1,131 @@ +import { TerminalSetupInfoModel, terminalSetupInfoZodModel } from "../../shared/model/terminal-setup-info.model"; +import { DefaultEventsMap, Socket } from "socket.io"; +import setupPodService from "./setup-services/setup-pod.service"; +import k3s from "../adapter/kubernetes-api.adapter"; +import * as k8s from '@kubernetes/client-node'; +import stream from 'stream'; +import { StreamUtils } from "@/shared/utils/stream.utils"; +import WebSocket from "ws"; + +interface TerminalStrean { + stdoutStream: stream.PassThrough; + stderrStream: stream.PassThrough; + stdinStream: stream.PassThrough; + streamInputKey: string; + streamOutputKey: string; + websocket: WebSocket.WebSocket; +} + +export class TerminalService { + activeStreams = new Map(); + + async streamLogs(socket: Socket) { + console.log('Client connected:', socket.id); + + const streamsOfSocket: TerminalStrean[] = []; + + socket.on('openTerminal', async (podInfo) => { + + const terminalInfo = terminalSetupInfoZodModel.parse(podInfo); + const streamInputKey = StreamUtils.getInputStreamName(terminalInfo); + const streamOutputKey = StreamUtils.getOutputStreamName(terminalInfo); + + const podReachable = await setupPodService.waitUntilPodIsRunningFailedOrSucceded(terminalInfo.namespace, terminalInfo.podName); + if (!podReachable) { + socket.emit(streamOutputKey); + return; + } + + const exec = new k8s.Exec(k3s.getKubeConfig()); + + const stdoutStream = new stream.PassThrough(); + const stderrStream = new stream.PassThrough(); + const stdinStream = new stream.PassThrough(); + + const websocket = await exec.exec( + terminalInfo.namespace, + terminalInfo.podName, + terminalInfo.containerName, + ['/bin/sh'], + stdoutStream, + stderrStream, + stdinStream, + true /* tty */, + (status: k8s.V1Status) => { + console.log('Exited with status:'); + console.log(JSON.stringify(status, null, 2)); + stderrStream!.end(); + stdoutStream!.end(); + stdinStream!.end(); + }, + ); + + stdoutStream.on('data', (chunk) => { + socket.emit(streamOutputKey, chunk.toString()); + }); + stderrStream.on('data', (chunk) => { + socket.emit(streamOutputKey, chunk.toString()); + }); + socket.on(streamInputKey, (data) => { + stdinStream!.write(data); + }); + + streamsOfSocket.push({ stdoutStream, stderrStream, stdinStream, streamInputKey, streamOutputKey, websocket }); + + + console.log(`Client ${socket.id} joined log stream for ${stdoutStream}`); + }); + + socket.on('closeTerminal', (podInfo) => { + const terminalInfo = terminalSetupInfoZodModel.parse(podInfo); + const streamInputKey = StreamUtils.getInputStreamName(terminalInfo); + + const streams = streamsOfSocket.find(stream => stream.streamInputKey === streamInputKey); + if (streams) { + this.deleteLogStream(streams); + } + }); + + socket.on('disconnecting', () => { + // Stop all log streams for this client + for (const stream of streamsOfSocket) { + this.deleteLogStream(stream); + } + }); + } + + + private deleteLogStream(streams: TerminalStrean) { + streams.stderrStream.end(); + streams.stdoutStream.end(); + streams.stdinStream.end(); + streams.websocket.close(); + + console.log(`Stopped log stream for ${streams.streamInputKey}.`); + } + /* + private async createLogStreamForPod(socket: Socket, + streamKey: string, inputInfo: TerminalSetupInfoModel) { + + + + logStream.on('data', (chunk) => { + socket.emit(streamKey, chunk.toString()); + }); + + logStream.on('data', (chunk) => { + socket.to(streamKey).emit(`${streamKey}`, chunk.toString()); + }); + + let k3sStreamRequest = await k3s.log.log(app.projectId, pod.podName, pod.containerName, logStream, { + follow: true, + pretty: false, + tailLines: 100, + }); + const retVal = { logStream, clients: 0, k3sStreamRequest }; + return retVal; + }*/ +} + +const terminalService = new TerminalService(); +export default terminalService; \ No newline at end of file diff --git a/src/shared/model/terminal-setup-info.model.ts b/src/shared/model/terminal-setup-info.model.ts new file mode 100644 index 0000000..a90308f --- /dev/null +++ b/src/shared/model/terminal-setup-info.model.ts @@ -0,0 +1,9 @@ +import { z } from "zod"; + +export const terminalSetupInfoZodModel = z.object({ + namespace: z.string().min(1), + podName: z.string().min(1), + containerName: z.string().min(1), +}); + +export type TerminalSetupInfoModel = z.infer; \ No newline at end of file diff --git a/src/shared/utils/stream.utils.ts b/src/shared/utils/stream.utils.ts new file mode 100644 index 0000000..b99bdbc --- /dev/null +++ b/src/shared/utils/stream.utils.ts @@ -0,0 +1,12 @@ +import { TerminalSetupInfoModel } from "../model/terminal-setup-info.model"; + +export class StreamUtils { + + static getInputStreamName(terminalInfo: TerminalSetupInfoModel) { + return `${terminalInfo.namespace}_${terminalInfo.podName}_${terminalInfo.containerName}_input`; + } + + static getOutputStreamName(terminalInfo: TerminalSetupInfoModel) { + return `${terminalInfo.namespace}_${terminalInfo.podName}_${terminalInfo.containerName}_output`; + } +} \ No newline at end of file diff --git a/src/socket-io.server.ts b/src/socket-io.server.ts index ac49b0a..1c4f5d8 100644 --- a/src/socket-io.server.ts +++ b/src/socket-io.server.ts @@ -1,12 +1,13 @@ import type http from "node:http"; import { Server } from "socket.io"; +import terminalService from "./server/services/terminal.service"; class SocketIoServer { initialize(server: http.Server) { const io = new Server(server); - const podLogsNamespace = io.of("/pod-logs"); + const podLogsNamespace = io.of("/pod-terminal"); podLogsNamespace.on("connection", (socket) => { - //logService.streamLogs(socket); + terminalService.streamLogs(socket); }); }; } diff --git a/src/websocket.server.ts b/src/websocket.server.ts new file mode 100644 index 0000000..f36cad7 --- /dev/null +++ b/src/websocket.server.ts @@ -0,0 +1,31 @@ +import { WebSocket } from "ws"; +import type http from "node:http"; + +export default async function initializeWebsocket(server: http.Server) { + + // Create a WebSocket server by passing the HTTP server + const wss = new WebSocket.Server({ server }); + + // Event handler for WebSocket connections + wss.on('connection', (ws) => { + console.log('A new client has connected.'); + + // Event handler for incoming messages from clients + ws.on('message', (message) => { + console.log(`Received: ${message}`); + + // Broadcast the received message to all connected clients + wss.clients.forEach((client) => { + if (client !== ws && client.readyState === WebSocket.OPEN) { + client.send(message); + } + }); + }); + + // Event handler for WebSocket connection closing + ws.on('close', () => { + console.log('A client has disconnected.'); + }); + }); + +} \ No newline at end of file