From 98b685fe1b78922d8b8baf517887f4dff06c476d Mon Sep 17 00:00:00 2001 From: Yuzu Date: Fri, 16 Jan 2026 12:32:36 +0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20style:=20auto=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .devcontainer/devcontainer.json | 52 ++-- .vscode/settings.json | 8 +- packages/k8s-operator/src/config/constants.ts | 44 ++-- .../src/controllers/base-controller.ts | 16 +- .../controllers/reverse-proxy-controller.ts | 77 +++--- .../src/controllers/server-controller.ts | 79 +++---- packages/k8s-operator/src/crds/rbac.ts | 112 ++++----- .../k8s-operator/src/crds/reverseProxy.ts | 120 +++++----- packages/k8s-operator/src/crds/server.ts | 98 ++++---- packages/k8s-operator/src/index.ts | 49 ++-- .../src/resources/reverseProxyServer.ts | 106 ++++----- packages/k8s-operator/src/resources/server.ts | 222 +++++++++--------- .../k8s-operator/src/scripts/apply-crds.ts | 32 +-- packages/k8s-operator/src/types/index.ts | 45 ++-- .../k8s-operator/src/utils/crd-registrar.ts | 211 +++++++++-------- packages/k8s-operator/src/utils/k8s-client.ts | 26 +- packages/k8s-operator/src/utils/memory.ts | 12 +- .../k8s-operator/src/utils/rbac-registrar.ts | 137 ++++++----- 18 files changed, 743 insertions(+), 703 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e7e1fdb..5078a93 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,31 +1,31 @@ { - "name": "Minikura Development", - "dockerComposeFile": "./docker-compose.yml", - "service": "devcontainer", - "workspaceFolder": "/workspace", - "remoteUser": "dev", + "name": "Minikura Development", + "dockerComposeFile": "./docker-compose.yml", + "service": "devcontainer", + "workspaceFolder": "/workspace", + "remoteUser": "dev", - "postCreateCommand": "bash /workspace/.devcontainer/post-create.sh", + "postCreateCommand": "bash /workspace/.devcontainer/post-create.sh", - "forwardPorts": [3000, 3001, 5432, 6443, 25565, 25577], + "forwardPorts": [3000, 3001, 5432, 6443, 25565, 25577], - "customizations": { - "vscode": { - "extensions": [ - "biomejs.biome", - "Prisma.prisma", - "ms-kubernetes-tools.vscode-kubernetes-tools", - "ms-azuretools.vscode-docker", - "bradlc.vscode-tailwindcss", - "redhat.vscode-yaml" - ], - "settings": { - "editor.defaultFormatter": "biomejs.biome", - "editor.formatOnSave": true, - "[prisma]": { - "editor.defaultFormatter": "Prisma.prisma" - } - } - } - } + "customizations": { + "vscode": { + "extensions": [ + "biomejs.biome", + "Prisma.prisma", + "ms-kubernetes-tools.vscode-kubernetes-tools", + "ms-azuretools.vscode-docker", + "bradlc.vscode-tailwindcss", + "redhat.vscode-yaml" + ], + "settings": { + "editor.defaultFormatter": "biomejs.biome", + "editor.formatOnSave": true, + "[prisma]": { + "editor.defaultFormatter": "Prisma.prisma" + } + } + } + } } diff --git a/.vscode/settings.json b/.vscode/settings.json index 2ed7829..0e7f275 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { - "editor.defaultFormatter": "biomejs.biome", - "[prisma]": { - "editor.defaultFormatter": "Prisma.prisma" - } + "editor.defaultFormatter": "biomejs.biome", + "[prisma]": { + "editor.defaultFormatter": "Prisma.prisma" + } } diff --git a/packages/k8s-operator/src/config/constants.ts b/packages/k8s-operator/src/config/constants.ts index c2b977e..581fbc6 100644 --- a/packages/k8s-operator/src/config/constants.ts +++ b/packages/k8s-operator/src/config/constants.ts @@ -1,49 +1,49 @@ import { dotenvLoad } from "dotenv-mono"; const dotenv = dotenvLoad(); -export const API_GROUP = 'minikura.kirameki.cafe'; -export const API_VERSION = 'v1alpha1'; +export const API_GROUP = "minikura.kirameki.cafe"; +export const API_VERSION = "v1alpha1"; export const KUBERNETES_NAMESPACE_ENV = process.env.KUBERNETES_NAMESPACE; -export const NAMESPACE = process.env.KUBERNETES_NAMESPACE || 'minikura'; +export const NAMESPACE = process.env.KUBERNETES_NAMESPACE || "minikura"; -export const ENABLE_CRD_REFLECTION = process.env.ENABLE_CRD_REFLECTION === 'true'; -export const SKIP_TLS_VERIFY = process.env.KUBERNETES_SKIP_TLS_VERIFY === 'true'; +export const ENABLE_CRD_REFLECTION = process.env.ENABLE_CRD_REFLECTION === "true"; +export const SKIP_TLS_VERIFY = process.env.KUBERNETES_SKIP_TLS_VERIFY === "true"; if (SKIP_TLS_VERIFY) { - process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; } // Resource types export const RESOURCE_TYPES = { MINECRAFT_SERVER: { - kind: 'MinecraftServer', - plural: 'minecraftservers', - singular: 'minecraftserver', - shortNames: ['mcs'], + kind: "MinecraftServer", + plural: "minecraftservers", + singular: "minecraftserver", + shortNames: ["mcs"], }, REVERSE_PROXY_SERVER: { - kind: 'ReverseProxyServer', - plural: 'reverseproxyservers', - singular: 'reverseproxyserver', - shortNames: ['rps'], + kind: "ReverseProxyServer", + plural: "reverseproxyservers", + singular: "reverseproxyserver", + shortNames: ["rps"], }, }; // Kubernetes resource label prefixes -export const LABEL_PREFIX = 'minikura.kirameki.cafe'; +export const LABEL_PREFIX = "minikura.kirameki.cafe"; // Polling intervals (in milliseconds) export const SYNC_INTERVAL = 30 * 1000; // 30 seconds export const IMAGES = { - MINECRAFT: 'itzg/minecraft-server', - REVERSE_PROXY: 'itzg/minecraft-server', + MINECRAFT: "itzg/minecraft-server", + REVERSE_PROXY: "itzg/minecraft-server", }; export const DEFAULTS = { - MEMORY: '1G', - CPU_REQUEST: '250m', - CPU_LIMIT: '1000m', - STORAGE_SIZE: '1Gi', -}; + MEMORY: "1G", + CPU_REQUEST: "250m", + CPU_LIMIT: "1000m", + STORAGE_SIZE: "1Gi", +}; diff --git a/packages/k8s-operator/src/controllers/base-controller.ts b/packages/k8s-operator/src/controllers/base-controller.ts index 9bb6267..b4848c1 100644 --- a/packages/k8s-operator/src/controllers/base-controller.ts +++ b/packages/k8s-operator/src/controllers/base-controller.ts @@ -1,6 +1,6 @@ -import { PrismaClient } from '@minikura/db'; -import { KubernetesClient } from '../utils/k8s-client'; -import { SYNC_INTERVAL } from '../config/constants'; +import type { PrismaClient } from "@minikura/db"; +import { KubernetesClient } from "../utils/k8s-client"; +import { SYNC_INTERVAL } from "../config/constants"; export abstract class BaseController { protected prisma: PrismaClient; @@ -19,16 +19,16 @@ export abstract class BaseController { */ public startWatching(): void { console.log(`Starting to watch for changes in ${this.getControllerName()}...`); - + // Initial sync - this.syncResources().catch(err => { + this.syncResources().catch((err) => { console.error(`Error during initial sync of ${this.getControllerName()}:`, err); }); - + // Polling interval for changes // TODO: Maybe there's a better way to do this this.intervalId = setInterval(() => { - this.syncResources().catch(err => { + this.syncResources().catch((err) => { console.error(`Error syncing ${this.getControllerName()}:`, err); }); }, SYNC_INTERVAL); @@ -54,4 +54,4 @@ export abstract class BaseController { * Sync resources from database to Kubernetes */ protected abstract syncResources(): Promise; -} \ No newline at end of file +} diff --git a/packages/k8s-operator/src/controllers/reverse-proxy-controller.ts b/packages/k8s-operator/src/controllers/reverse-proxy-controller.ts index 292e7b8..1382ef9 100644 --- a/packages/k8s-operator/src/controllers/reverse-proxy-controller.ts +++ b/packages/k8s-operator/src/controllers/reverse-proxy-controller.ts @@ -1,8 +1,11 @@ -import { PrismaClient } from '@minikura/db'; -import type { ReverseProxyServer, CustomEnvironmentVariable } from '@minikura/db'; -import { BaseController } from './base-controller'; -import type { ReverseProxyConfig } from '../types'; -import { createReverseProxyServer, deleteReverseProxyServer } from '../resources/reverseProxyServer'; +import type { PrismaClient } from "@minikura/db"; +import type { ReverseProxyServer, CustomEnvironmentVariable } from "@minikura/db"; +import { BaseController } from "./base-controller"; +import type { ReverseProxyConfig } from "../types"; +import { + createReverseProxyServer, + deleteReverseProxyServer, +} from "../resources/reverseProxyServer"; type ReverseProxyWithEnvVars = ReverseProxyServer & { env_variables: CustomEnvironmentVariable[]; @@ -16,7 +19,7 @@ export class ReverseProxyController extends BaseController { } protected getControllerName(): string { - return 'ReverseProxyController'; + return "ReverseProxyController"; } protected async syncResources(): Promise { @@ -25,31 +28,35 @@ export class ReverseProxyController extends BaseController { const coreApi = this.k8sClient.getCoreApi(); const networkingApi = this.k8sClient.getNetworkingApi(); - const proxies = await this.prisma.reverseProxyServer.findMany({ + const proxies = (await this.prisma.reverseProxyServer.findMany({ include: { env_variables: true, - } - }) as ReverseProxyWithEnvVars[]; - - const currentProxyIds = new Set(proxies.map(proxy => proxy.id)); - + }, + })) as ReverseProxyWithEnvVars[]; + + const currentProxyIds = new Set(proxies.map((proxy) => proxy.id)); + // Delete reverse proxy servers that are no longer in the database for (const [proxyId, proxy] of this.deployedProxies.entries()) { if (!currentProxyIds.has(proxyId)) { - console.log(`Reverse proxy server ${proxy.id} (${proxyId}) has been removed from the database, deleting from Kubernetes...`); + console.log( + `Reverse proxy server ${proxy.id} (${proxyId}) has been removed from the database, deleting from Kubernetes...` + ); await deleteReverseProxyServer(proxy.id, proxy.type, appsApi, coreApi, this.namespace); this.deployedProxies.delete(proxyId); } } - + // Create or update reverse proxy servers that are in the database for (const proxy of proxies) { const deployedProxy = this.deployedProxies.get(proxy.id); - + // If proxy doesn't exist yet or has been updated if (!deployedProxy || this.hasProxyChanged(deployedProxy, proxy)) { - console.log(`${!deployedProxy ? 'Creating' : 'Updating'} reverse proxy server ${proxy.id} (${proxy.id}) in Kubernetes...`); - + console.log( + `${!deployedProxy ? "Creating" : "Updating"} reverse proxy server ${proxy.id} (${proxy.id}) in Kubernetes...` + ); + const proxyConfig: ReverseProxyConfig = { id: proxy.id, external_address: proxy.external_address, @@ -59,55 +66,55 @@ export class ReverseProxyController extends BaseController { apiKey: proxy.api_key, type: proxy.type, memory: proxy.memory, - env_variables: proxy.env_variables?.map(ev => ({ + env_variables: proxy.env_variables?.map((ev) => ({ key: ev.key, - value: ev.value - })) + value: ev.value, + })), }; - + await createReverseProxyServer( - proxyConfig, - appsApi, - coreApi, - networkingApi, + proxyConfig, + appsApi, + coreApi, + networkingApi, this.namespace ); - + // Update cache this.deployedProxies.set(proxy.id, { ...proxy }); } } } catch (error) { - console.error('Error syncing reverse proxy servers:', error); + console.error("Error syncing reverse proxy servers:", error); throw error; } } private hasProxyChanged( - oldProxy: ReverseProxyWithEnvVars, + oldProxy: ReverseProxyWithEnvVars, newProxy: ReverseProxyWithEnvVars ): boolean { // Check basic properties - const basicPropsChanged = + const basicPropsChanged = oldProxy.external_address !== newProxy.external_address || oldProxy.external_port !== newProxy.external_port || oldProxy.listen_port !== newProxy.listen_port || oldProxy.description !== newProxy.description; - + if (basicPropsChanged) return true; - + // Check if environment variables have changed const oldEnvVars = oldProxy.env_variables || []; const newEnvVars = newProxy.env_variables || []; - + if (oldEnvVars.length !== newEnvVars.length) return true; for (const newEnv of newEnvVars) { - const oldEnv = oldEnvVars.find(e => e.key === newEnv.key); + const oldEnv = oldEnvVars.find((e) => e.key === newEnv.key); if (!oldEnv || oldEnv.value !== newEnv.value) { return true; } } - + return false; } -} \ No newline at end of file +} diff --git a/packages/k8s-operator/src/controllers/server-controller.ts b/packages/k8s-operator/src/controllers/server-controller.ts index f3ca6f2..72f4873 100644 --- a/packages/k8s-operator/src/controllers/server-controller.ts +++ b/packages/k8s-operator/src/controllers/server-controller.ts @@ -1,8 +1,8 @@ -import { PrismaClient, ServerType } from '@minikura/db'; -import type { Server, CustomEnvironmentVariable } from '@minikura/db'; -import { BaseController } from './base-controller'; -import type { ServerConfig } from '../types'; -import { createServer, deleteServer } from '../resources/server'; +import { type PrismaClient, ServerType } from "@minikura/db"; +import type { Server, CustomEnvironmentVariable } from "@minikura/db"; +import { BaseController } from "./base-controller"; +import type { ServerConfig } from "../types"; +import { createServer, deleteServer } from "../resources/server"; type ServerWithEnvVars = Server & { env_variables: CustomEnvironmentVariable[]; @@ -16,7 +16,7 @@ export class ServerController extends BaseController { } protected getControllerName(): string { - return 'ServerController'; + return "ServerController"; } protected async syncResources(): Promise { @@ -25,31 +25,35 @@ export class ServerController extends BaseController { const coreApi = this.k8sClient.getCoreApi(); const networkingApi = this.k8sClient.getNetworkingApi(); - const servers = await this.prisma.server.findMany({ + const servers = (await this.prisma.server.findMany({ include: { env_variables: true, - } - }) as ServerWithEnvVars[]; - - const currentServerIds = new Set(servers.map(server => server.id)); - + }, + })) as ServerWithEnvVars[]; + + const currentServerIds = new Set(servers.map((server) => server.id)); + // Delete servers that are no longer in the database for (const [serverId, server] of this.deployedServers.entries()) { if (!currentServerIds.has(serverId)) { - console.log(`Server ${server.id} (${serverId}) has been removed from the database, deleting from Kubernetes...`); + console.log( + `Server ${server.id} (${serverId}) has been removed from the database, deleting from Kubernetes...` + ); await deleteServer(serverId, server.id, appsApi, coreApi, this.namespace); this.deployedServers.delete(serverId); } } - + // Create or update servers that are in the database for (const server of servers) { const deployedServer = this.deployedServers.get(server.id); - + // If server doesn't exist yet or has been updated if (!deployedServer || this.hasServerChanged(deployedServer, server)) { - console.log(`${!deployedServer ? 'Creating' : 'Updating'} server ${server.id} (${server.id}) in Kubernetes...`); - + console.log( + `${!deployedServer ? "Creating" : "Updating"} server ${server.id} (${server.id}) in Kubernetes...` + ); + const serverConfig: ServerConfig = { id: server.id, type: server.type, @@ -57,57 +61,48 @@ export class ServerController extends BaseController { description: server.description, listen_port: server.listen_port, memory: server.memory, - env_variables: server.env_variables?.map(ev => ({ + env_variables: server.env_variables?.map((ev) => ({ key: ev.key, - value: ev.value - })) + value: ev.value, + })), }; - - await createServer( - serverConfig, - appsApi, - coreApi, - networkingApi, - this.namespace - ); - + + await createServer(serverConfig, appsApi, coreApi, networkingApi, this.namespace); + // Update cache this.deployedServers.set(server.id, { ...server }); } } } catch (error) { - console.error('Error syncing servers:', error); + console.error("Error syncing servers:", error); throw error; } } - private hasServerChanged( - oldServer: ServerWithEnvVars, - newServer: ServerWithEnvVars - ): boolean { + private hasServerChanged(oldServer: ServerWithEnvVars, newServer: ServerWithEnvVars): boolean { // Check basic properties - const basicPropsChanged = + const basicPropsChanged = oldServer.type !== newServer.type || oldServer.listen_port !== newServer.listen_port || oldServer.description !== newServer.description; - + if (basicPropsChanged) return true; - + // Check if environment variables have changed const oldEnvVars = oldServer.env_variables || []; const newEnvVars = newServer.env_variables || []; - + // Check if the number of env vars has changed if (oldEnvVars.length !== newEnvVars.length) return true; - + // Check if any of the existing env vars have changed for (const newEnv of newEnvVars) { - const oldEnv = oldEnvVars.find(e => e.key === newEnv.key); + const oldEnv = oldEnvVars.find((e) => e.key === newEnv.key); if (!oldEnv || oldEnv.value !== newEnv.value) { return true; } } - + return false; } -} \ No newline at end of file +} diff --git a/packages/k8s-operator/src/crds/rbac.ts b/packages/k8s-operator/src/crds/rbac.ts index 0b27d58..c467895 100644 --- a/packages/k8s-operator/src/crds/rbac.ts +++ b/packages/k8s-operator/src/crds/rbac.ts @@ -1,11 +1,11 @@ -import { NAMESPACE } from '../config/constants'; +import { NAMESPACE } from "../config/constants"; /** * Namespace definition */ export const minikuraNamespace = { - apiVersion: 'v1', - kind: 'Namespace', + apiVersion: "v1", + kind: "Namespace", metadata: { name: NAMESPACE, }, @@ -15,10 +15,10 @@ export const minikuraNamespace = { * Service account */ export const minikuraServiceAccount = { - apiVersion: 'v1', - kind: 'ServiceAccount', + apiVersion: "v1", + kind: "ServiceAccount", metadata: { - name: 'minikura-operator', + name: "minikura-operator", namespace: NAMESPACE, }, }; @@ -27,41 +27,41 @@ export const minikuraServiceAccount = { * Cluster role */ export const minikuraClusterRole = { - apiVersion: 'rbac.authorization.k8s.io/v1', - kind: 'ClusterRole', + apiVersion: "rbac.authorization.k8s.io/v1", + kind: "ClusterRole", metadata: { - name: 'minikura-operator-role', + name: "minikura-operator-role", }, rules: [ { - apiGroups: [''], - resources: ['configmaps', 'services', 'secrets'], - verbs: ['get', 'list', 'watch', 'create', 'update', 'patch', 'delete'], + apiGroups: [""], + resources: ["configmaps", "services", "secrets"], + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"], }, { - apiGroups: ['apps'], - resources: ['deployments', 'statefulsets'], - verbs: ['get', 'list', 'watch', 'create', 'update', 'patch', 'delete'], + apiGroups: ["apps"], + resources: ["deployments", "statefulsets"], + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"], }, { - apiGroups: ['networking.k8s.io'], - resources: ['ingresses'], - verbs: ['get', 'list', 'watch', 'create', 'update', 'patch', 'delete'], + apiGroups: ["networking.k8s.io"], + resources: ["ingresses"], + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"], }, { - apiGroups: ['apiextensions.k8s.io'], - resources: ['customresourcedefinitions'], - verbs: ['get', 'list', 'watch', 'create', 'update', 'patch', 'delete'], + apiGroups: ["apiextensions.k8s.io"], + resources: ["customresourcedefinitions"], + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"], }, { - apiGroups: ['minikura.kirameki.cafe'], - resources: ['minecraftservers', 'velocityproxies'], - verbs: ['get', 'list', 'watch', 'create', 'update', 'patch', 'delete'], + apiGroups: ["minikura.kirameki.cafe"], + resources: ["minecraftservers", "velocityproxies"], + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"], }, { - apiGroups: ['minikura.kirameki.cafe'], - resources: ['minecraftservers/status', 'velocityproxies/status'], - verbs: ['get', 'update', 'patch'], + apiGroups: ["minikura.kirameki.cafe"], + resources: ["minecraftservers/status", "velocityproxies/status"], + verbs: ["get", "update", "patch"], }, ], }; @@ -70,22 +70,22 @@ export const minikuraClusterRole = { * Cluster role binding */ export const minikuraClusterRoleBinding = { - apiVersion: 'rbac.authorization.k8s.io/v1', - kind: 'ClusterRoleBinding', + apiVersion: "rbac.authorization.k8s.io/v1", + kind: "ClusterRoleBinding", metadata: { - name: 'minikura-operator-role-binding', + name: "minikura-operator-role-binding", }, subjects: [ { - kind: 'ServiceAccount', - name: 'minikura-operator', + kind: "ServiceAccount", + name: "minikura-operator", namespace: NAMESPACE, }, ], roleRef: { - kind: 'ClusterRole', - name: 'minikura-operator-role', - apiGroup: 'rbac.authorization.k8s.io', + kind: "ClusterRole", + name: "minikura-operator-role", + apiGroup: "rbac.authorization.k8s.io", }, }; @@ -93,70 +93,70 @@ export const minikuraClusterRoleBinding = { * Deployment for the Minikura operator */ export const minikuraOperatorDeployment = { - apiVersion: 'apps/v1', - kind: 'Deployment', + apiVersion: "apps/v1", + kind: "Deployment", metadata: { - name: 'minikura-operator', + name: "minikura-operator", namespace: NAMESPACE, }, spec: { replicas: 1, selector: { matchLabels: { - app: 'minikura-operator', + app: "minikura-operator", }, }, template: { metadata: { labels: { - app: 'minikura-operator', + app: "minikura-operator", }, }, spec: { - serviceAccountName: 'minikura-operator', + serviceAccountName: "minikura-operator", containers: [ { - name: 'operator', - image: '${REGISTRY_URL}/minikura-operator:latest', + name: "operator", + image: "${REGISTRY_URL}/minikura-operator:latest", env: [ { - name: 'DATABASE_URL', + name: "DATABASE_URL", valueFrom: { secretKeyRef: { - name: 'minikura-operator-secrets', - key: 'DATABASE_URL', + name: "minikura-operator-secrets", + key: "DATABASE_URL", }, }, }, { - name: 'KUBERNETES_NAMESPACE', + name: "KUBERNETES_NAMESPACE", value: NAMESPACE, }, { - name: 'USE_CRDS', - value: 'true', + name: "USE_CRDS", + value: "true", }, ], resources: { requests: { - memory: '256Mi', - cpu: '200m', + memory: "256Mi", + cpu: "200m", }, limits: { - memory: '512Mi', - cpu: '500m', + memory: "512Mi", + cpu: "500m", }, }, livenessProbe: { exec: { - command: ['bun', '-e', "console.log('Health check')"], + command: ["bun", "-e", "console.log('Health check')"], }, initialDelaySeconds: 30, periodSeconds: 30, }, readinessProbe: { exec: { - command: ['bun', '-e', "console.log('Ready check')"], + command: ["bun", "-e", "console.log('Ready check')"], }, initialDelaySeconds: 5, periodSeconds: 10, @@ -166,4 +166,4 @@ export const minikuraOperatorDeployment = { }, }, }, -}; \ No newline at end of file +}; diff --git a/packages/k8s-operator/src/crds/reverseProxy.ts b/packages/k8s-operator/src/crds/reverseProxy.ts index 247c4c7..51252b8 100644 --- a/packages/k8s-operator/src/crds/reverseProxy.ts +++ b/packages/k8s-operator/src/crds/reverseProxy.ts @@ -1,8 +1,8 @@ -import { API_GROUP, API_VERSION, RESOURCE_TYPES } from '../config/constants'; +import { API_GROUP, API_VERSION, RESOURCE_TYPES } from "../config/constants"; export const REVERSE_PROXY_SERVER_CRD = { - apiVersion: 'apiextensions.k8s.io/v1', - kind: 'CustomResourceDefinition', + apiVersion: "apiextensions.k8s.io/v1", + kind: "CustomResourceDefinition", metadata: { name: `${RESOURCE_TYPES.REVERSE_PROXY_SERVER.plural}.${API_GROUP}`, }, @@ -15,67 +15,67 @@ export const REVERSE_PROXY_SERVER_CRD = { storage: true, schema: { openAPIV3Schema: { - type: 'object', + type: "object", properties: { spec: { - type: 'object', - required: ['id', 'external_address', 'external_port'], + type: "object", + required: ["id", "external_address", "external_port"], properties: { id: { - type: 'string', - pattern: '^[a-zA-Z0-9-_]+$', - description: 'ID of the reverse proxy server', + type: "string", + pattern: "^[a-zA-Z0-9-_]+$", + description: "ID of the reverse proxy server", }, description: { - type: 'string', + type: "string", nullable: true, - description: 'Optional description of the server', + description: "Optional description of the server", }, external_address: { - type: 'string', - description: 'External address of the proxy server', + type: "string", + description: "External address of the proxy server", }, external_port: { - type: 'integer', + type: "integer", minimum: 1, maximum: 65535, - description: 'External port of the proxy server', + description: "External port of the proxy server", }, listen_port: { - type: 'integer', + type: "integer", minimum: 1, maximum: 65535, default: 25565, nullable: true, - description: 'Port the proxy server listens on internally', + description: "Port the proxy server listens on internally", }, type: { - type: 'string', - enum: ['VELOCITY', 'BUNGEECORD'], - default: 'VELOCITY', + type: "string", + enum: ["VELOCITY", "BUNGEECORD"], + default: "VELOCITY", nullable: true, - description: 'Type of the reverse proxy server', + description: "Type of the reverse proxy server", }, memory: { - type: 'string', - default: '512M', + type: "string", + default: "512M", nullable: true, - description: 'Memory allocation for the server', + description: "Memory allocation for the server", }, environmentVariables: { - type: 'array', + type: "array", nullable: true, items: { - type: 'object', - required: ['key', 'value'], + type: "object", + required: ["key", "value"], properties: { key: { - type: 'string', - description: 'Environment variable key', + type: "string", + description: "Environment variable key", }, value: { - type: 'string', - description: 'Environment variable value', + type: "string", + description: "Environment variable value", }, }, }, @@ -83,33 +83,33 @@ export const REVERSE_PROXY_SERVER_CRD = { }, }, status: { - type: 'object', + type: "object", nullable: true, properties: { phase: { - type: 'string', - enum: ['Pending', 'Running', 'Failed'], - description: 'Current phase of the server', + type: "string", + enum: ["Pending", "Running", "Failed"], + description: "Current phase of the server", }, message: { - type: 'string', + type: "string", nullable: true, - description: 'Detailed message about the current status', + description: "Detailed message about the current status", }, apiKey: { - type: 'string', + type: "string", nullable: true, - description: 'API key for server communication', + description: "API key for server communication", }, internalId: { - type: 'string', + type: "string", nullable: true, - description: 'Internal ID assigned by Minikura', + description: "Internal ID assigned by Minikura", }, lastSyncedAt: { - type: 'string', + type: "string", nullable: true, - description: 'Last time the server was synced with Kubernetes', + description: "Last time the server was synced with Kubernetes", }, }, }, @@ -118,34 +118,34 @@ export const REVERSE_PROXY_SERVER_CRD = { }, additionalPrinterColumns: [ { - name: 'Type', - type: 'string', - jsonPath: '.spec.type', + name: "Type", + type: "string", + jsonPath: ".spec.type", }, { - name: 'External Address', - type: 'string', - jsonPath: '.spec.external_address', + name: "External Address", + type: "string", + jsonPath: ".spec.external_address", }, { - name: 'External Port', - type: 'integer', - jsonPath: '.spec.external_port', + name: "External Port", + type: "integer", + jsonPath: ".spec.external_port", }, { - name: 'Status', - type: 'string', - jsonPath: '.status.phase', + name: "Status", + type: "string", + jsonPath: ".status.phase", }, { - name: 'Age', - type: 'date', - jsonPath: '.metadata.creationTimestamp', + name: "Age", + type: "date", + jsonPath: ".metadata.creationTimestamp", }, ], }, ], - scope: 'Namespaced', + scope: "Namespaced", names: { singular: RESOURCE_TYPES.REVERSE_PROXY_SERVER.singular, plural: RESOURCE_TYPES.REVERSE_PROXY_SERVER.plural, @@ -153,4 +153,4 @@ export const REVERSE_PROXY_SERVER_CRD = { shortNames: RESOURCE_TYPES.REVERSE_PROXY_SERVER.shortNames, }, }, -}; \ No newline at end of file +}; diff --git a/packages/k8s-operator/src/crds/server.ts b/packages/k8s-operator/src/crds/server.ts index 9474c8f..e2ea1cc 100644 --- a/packages/k8s-operator/src/crds/server.ts +++ b/packages/k8s-operator/src/crds/server.ts @@ -1,8 +1,8 @@ -import { API_GROUP, API_VERSION, RESOURCE_TYPES } from '../config/constants'; +import { API_GROUP, API_VERSION, RESOURCE_TYPES } from "../config/constants"; export const MINECRAFT_SERVER_CRD = { - apiVersion: 'apiextensions.k8s.io/v1', - kind: 'CustomResourceDefinition', + apiVersion: "apiextensions.k8s.io/v1", + kind: "CustomResourceDefinition", metadata: { name: `${RESOURCE_TYPES.MINECRAFT_SERVER.plural}.${API_GROUP}`, }, @@ -15,53 +15,53 @@ export const MINECRAFT_SERVER_CRD = { storage: true, schema: { openAPIV3Schema: { - type: 'object', + type: "object", properties: { spec: { - type: 'object', - required: ['id', 'type', 'listen_port'], + type: "object", + required: ["id", "type", "listen_port"], properties: { id: { - type: 'string', - pattern: '^[a-zA-Z0-9-_]+$', - description: 'ID of the Minecraft server', + type: "string", + pattern: "^[a-zA-Z0-9-_]+$", + description: "ID of the Minecraft server", }, description: { - type: 'string', + type: "string", nullable: true, - description: 'Optional description of the server', + description: "Optional description of the server", }, listen_port: { - type: 'integer', + type: "integer", minimum: 1, maximum: 65535, - description: 'Port the server listens on', + description: "Port the server listens on", }, type: { - type: 'string', - enum: ['STATEFUL', 'STATELESS'], - description: 'Type of the server', + type: "string", + enum: ["STATEFUL", "STATELESS"], + description: "Type of the server", }, memory: { - type: 'string', + type: "string", nullable: true, - default: '1G', - description: 'Memory allocation for the server', + default: "1G", + description: "Memory allocation for the server", }, environmentVariables: { - type: 'array', + type: "array", nullable: true, items: { - type: 'object', - required: ['key', 'value'], + type: "object", + required: ["key", "value"], properties: { key: { - type: 'string', - description: 'Environment variable key', + type: "string", + description: "Environment variable key", }, value: { - type: 'string', - description: 'Environment variable value', + type: "string", + description: "Environment variable value", }, }, }, @@ -69,33 +69,33 @@ export const MINECRAFT_SERVER_CRD = { }, }, status: { - type: 'object', + type: "object", nullable: true, properties: { phase: { - type: 'string', - enum: ['Pending', 'Running', 'Failed'], - description: 'Current phase of the server', + type: "string", + enum: ["Pending", "Running", "Failed"], + description: "Current phase of the server", }, message: { - type: 'string', + type: "string", nullable: true, - description: 'Detailed message about the current status', + description: "Detailed message about the current status", }, apiKey: { - type: 'string', + type: "string", nullable: true, - description: 'API key for server communication', + description: "API key for server communication", }, internalId: { - type: 'string', + type: "string", nullable: true, - description: 'Internal ID assigned by Minikura', + description: "Internal ID assigned by Minikura", }, lastSyncedAt: { - type: 'string', + type: "string", nullable: true, - description: 'Last time the server was synced with Kubernetes', + description: "Last time the server was synced with Kubernetes", }, }, }, @@ -104,24 +104,24 @@ export const MINECRAFT_SERVER_CRD = { }, additionalPrinterColumns: [ { - name: 'Type', - type: 'string', - jsonPath: '.spec.type', + name: "Type", + type: "string", + jsonPath: ".spec.type", }, { - name: 'Status', - type: 'string', - jsonPath: '.status.phase', + name: "Status", + type: "string", + jsonPath: ".status.phase", }, { - name: 'Age', - type: 'date', - jsonPath: '.metadata.creationTimestamp', + name: "Age", + type: "date", + jsonPath: ".metadata.creationTimestamp", }, ], }, ], - scope: 'Namespaced', + scope: "Namespaced", names: { singular: RESOURCE_TYPES.MINECRAFT_SERVER.singular, plural: RESOURCE_TYPES.MINECRAFT_SERVER.plural, @@ -129,4 +129,4 @@ export const MINECRAFT_SERVER_CRD = { shortNames: RESOURCE_TYPES.MINECRAFT_SERVER.shortNames, }, }, -}; \ No newline at end of file +}; diff --git a/packages/k8s-operator/src/index.ts b/packages/k8s-operator/src/index.ts index 7da6edd..1388b93 100644 --- a/packages/k8s-operator/src/index.ts +++ b/packages/k8s-operator/src/index.ts @@ -1,29 +1,29 @@ import { dotenvLoad } from "dotenv-mono"; const dotenv = dotenvLoad(); -import { NAMESPACE, KUBERNETES_NAMESPACE_ENV, ENABLE_CRD_REFLECTION } from './config/constants'; +import { NAMESPACE, KUBERNETES_NAMESPACE_ENV, ENABLE_CRD_REFLECTION } from "./config/constants"; import { prisma } from "@minikura/db"; -import { KubernetesClient } from './utils/k8s-client'; -import { ServerController } from './controllers/server-controller'; -import { ReverseProxyController } from './controllers/reverse-proxy-controller'; -import { setupCRDRegistration } from './utils/crd-registrar'; +import { KubernetesClient } from "./utils/k8s-client"; +import { ServerController } from "./controllers/server-controller"; +import { ReverseProxyController } from "./controllers/reverse-proxy-controller"; +import { setupCRDRegistration } from "./utils/crd-registrar"; async function main() { - console.log('Starting Minikura Kubernetes Operator...'); + console.log("Starting Minikura Kubernetes Operator..."); console.log(`Using namespace: ${NAMESPACE}`); - + try { const k8sClient = KubernetesClient.getInstance(); - console.log('Connected to Kubernetes cluster'); + console.log("Connected to Kubernetes cluster"); const serverController = new ServerController(prisma, NAMESPACE); const reverseProxyController = new ReverseProxyController(prisma, NAMESPACE); - + serverController.startWatching(); reverseProxyController.startWatching(); if (ENABLE_CRD_REFLECTION) { - console.log('CRD reflection enabled - will create CRDs to reflect database state'); + console.log("CRD reflection enabled - will create CRDs to reflect database state"); try { await setupCRDRegistration(prisma, k8sClient, NAMESPACE); } catch (error: any) { @@ -32,25 +32,26 @@ async function main() { console.error(`Response status: ${error.response.statusCode}`); console.error(`Response body: ${JSON.stringify(error.response.body)}`); } - console.error('Continuing operation without CRD reflection'); - console.log('Kubernetes resources will still be created/updated, but CRD reflection is disabled'); + console.error("Continuing operation without CRD reflection"); + console.log( + "Kubernetes resources will still be created/updated, but CRD reflection is disabled" + ); } } - - console.log('Minikura Kubernetes Operator is running'); - - process.on('SIGINT', gracefulShutdown); - process.on('SIGTERM', gracefulShutdown); - + + console.log("Minikura Kubernetes Operator is running"); + + process.on("SIGINT", gracefulShutdown); + process.on("SIGTERM", gracefulShutdown); + function gracefulShutdown() { - console.log('Shutting down operator gracefully...'); + console.log("Shutting down operator gracefully..."); serverController.stopWatching(); reverseProxyController.stopWatching(); prisma.$disconnect(); - console.log('Resources released, exiting...'); + console.log("Resources released, exiting..."); process.exit(0); } - } catch (error: any) { console.error(`Failed to start Minikura Kubernetes Operator: ${error.message}`); if (error.response) { @@ -64,7 +65,7 @@ async function main() { } } -main().catch(error => { - console.error('Unhandled error:', error); +main().catch((error) => { + console.error("Unhandled error:", error); process.exit(1); -}); \ No newline at end of file +}); diff --git a/packages/k8s-operator/src/resources/reverseProxyServer.ts b/packages/k8s-operator/src/resources/reverseProxyServer.ts index 73a5816..c05e122 100644 --- a/packages/k8s-operator/src/resources/reverseProxyServer.ts +++ b/packages/k8s-operator/src/resources/reverseProxyServer.ts @@ -1,8 +1,8 @@ -import * as k8s from '@kubernetes/client-node'; -import { ReverseProxyServerType } from '@minikura/db'; -import { LABEL_PREFIX } from '../config/constants'; -import { calculateJavaMemory, convertToK8sFormat } from '../utils/memory'; -import type { ReverseProxyConfig } from '../types'; +import type * as k8s from "@kubernetes/client-node"; +import type { ReverseProxyServerType } from "@minikura/db"; +import { LABEL_PREFIX } from "../config/constants"; +import { calculateJavaMemory, convertToK8sFormat } from "../utils/memory"; +import type { ReverseProxyConfig } from "../types"; export async function createReverseProxyServer( server: ReverseProxyConfig, @@ -12,13 +12,13 @@ export async function createReverseProxyServer( namespace: string ): Promise { console.log(`Creating reverse proxy server ${server.id} in namespace '${namespace}'`); - + const serverType = server.type.toLowerCase(); const serverName = `${serverType}-${server.id}`; - + const configMap = { - apiVersion: 'v1', - kind: 'ConfigMap', + apiVersion: "v1", + kind: "ConfigMap", metadata: { name: `${serverName}-config`, namespace: namespace, @@ -26,13 +26,13 @@ export async function createReverseProxyServer( app: serverName, [`${LABEL_PREFIX}/server-type`]: serverType, [`${LABEL_PREFIX}/proxy-id`]: server.id, - } + }, }, data: { - 'minikura-api-key': server.apiKey, - } + "minikura-api-key": server.apiKey, + }, }; - + try { await coreApi.createNamespacedConfigMap(namespace, configMap); console.log(`Created ConfigMap for reverse proxy server ${server.id}`); @@ -45,11 +45,11 @@ export async function createReverseProxyServer( throw error; } } - + // Create Service for the reverse proxy - Always LoadBalancer for now const service = { - apiVersion: 'v1', - kind: 'Service', + apiVersion: "v1", + kind: "Service", metadata: { name: serverName, namespace: namespace, @@ -57,7 +57,7 @@ export async function createReverseProxyServer( app: serverName, [`${LABEL_PREFIX}/server-type`]: serverType, [`${LABEL_PREFIX}/proxy-id`]: server.id, - } + }, }, spec: { selector: { @@ -67,14 +67,14 @@ export async function createReverseProxyServer( { port: server.external_port, targetPort: server.listen_port, - protocol: 'TCP', - name: 'minecraft', - } + protocol: "TCP", + name: "minecraft", + }, ], - type: 'LoadBalancer', - } + type: "LoadBalancer", + }, }; - + try { await coreApi.createNamespacedService(namespace, service); console.log(`Created Service for reverse proxy server ${server.id}`); @@ -87,11 +87,11 @@ export async function createReverseProxyServer( throw error; } } - + // Create Deployment const deployment = { - apiVersion: 'apps/v1', - kind: 'Deployment', + apiVersion: "apps/v1", + kind: "Deployment", metadata: { name: serverName, namespace: namespace, @@ -99,14 +99,14 @@ export async function createReverseProxyServer( app: serverName, [`${LABEL_PREFIX}/server-type`]: serverType, [`${LABEL_PREFIX}/proxy-id`]: server.id, - } + }, }, spec: { replicas: 1, selector: { matchLabels: { app: serverName, - } + }, }, template: { metadata: { @@ -114,61 +114,61 @@ export async function createReverseProxyServer( app: serverName, [`${LABEL_PREFIX}/server-type`]: serverType, [`${LABEL_PREFIX}/proxy-id`]: server.id, - } + }, }, spec: { containers: [ { name: serverType, - image: 'itzg/mc-proxy:latest', + image: "itzg/mc-proxy:latest", ports: [ { containerPort: server.listen_port, - name: 'minecraft', - } + name: "minecraft", + }, ], env: [ { - name: 'TYPE', + name: "TYPE", value: server.type, }, { - name: 'NETWORKADDRESS_CACHE_TTL', - value: '30', + name: "NETWORKADDRESS_CACHE_TTL", + value: "30", }, { - name: 'MEMORY', - value: calculateJavaMemory(server.memory || '512M', 0.8), + name: "MEMORY", + value: calculateJavaMemory(server.memory || "512M", 0.8), }, - ...(server.env_variables || []).map(ev => ({ + ...(server.env_variables || []).map((ev) => ({ name: ev.key, value: ev.value, })), ], readinessProbe: { tcpSocket: { - port: server.listen_port, + port: server.listen_port, }, initialDelaySeconds: 30, periodSeconds: 10, }, resources: { requests: { - memory: convertToK8sFormat(server.memory || "512M"), + memory: convertToK8sFormat(server.memory || "512M"), cpu: "250m", }, limits: { - memory: convertToK8sFormat(server.memory || "512M"), + memory: convertToK8sFormat(server.memory || "512M"), cpu: "500m", - } - } - } - ] - } - } - } + }, + }, + }, + ], + }, + }, + }, }; - + try { await appsApi.createNamespacedDeployment(namespace, deployment); console.log(`Created Deployment for reverse proxy server ${server.id}`); @@ -192,7 +192,7 @@ export async function deleteReverseProxyServer( ): Promise { const serverType = proxyType.toLowerCase(); const name = `${serverType}-${proxyId}`; - + try { await appsApi.deleteNamespacedDeployment(name, namespace); console.log(`Deleted Deployment for reverse proxy server ${proxyId}`); @@ -201,7 +201,7 @@ export async function deleteReverseProxyServer( console.error(`Error deleting Deployment for reverse proxy server ${proxyId}:`, error); } } - + try { await coreApi.deleteNamespacedService(name, namespace); console.log(`Deleted Service for reverse proxy server ${proxyId}`); @@ -210,7 +210,7 @@ export async function deleteReverseProxyServer( console.error(`Error deleting Service for reverse proxy server ${proxyId}:`, error); } } - + try { await coreApi.deleteNamespacedConfigMap(`${name}-config`, namespace); console.log(`Deleted ConfigMap for reverse proxy server ${proxyId}`); @@ -219,4 +219,4 @@ export async function deleteReverseProxyServer( console.error(`Error deleting ConfigMap for reverse proxy server ${proxyId}:`, error); } } -} \ No newline at end of file +} diff --git a/packages/k8s-operator/src/resources/server.ts b/packages/k8s-operator/src/resources/server.ts index 5a91664..398b10a 100644 --- a/packages/k8s-operator/src/resources/server.ts +++ b/packages/k8s-operator/src/resources/server.ts @@ -1,8 +1,8 @@ -import * as k8s from '@kubernetes/client-node'; -import { ServerType } from '@minikura/db'; -import { LABEL_PREFIX } from '../config/constants'; -import { calculateJavaMemory, convertToK8sFormat } from '../utils/memory'; -import type { ServerConfig } from '../types'; +import type * as k8s from "@kubernetes/client-node"; +import { ServerType } from "@minikura/db"; +import { LABEL_PREFIX } from "../config/constants"; +import { calculateJavaMemory, convertToK8sFormat } from "../utils/memory"; +import type { ServerConfig } from "../types"; export async function createServer( server: ServerConfig, @@ -14,8 +14,8 @@ export async function createServer( const serverName = `minecraft-${server.id}`; const configMap = { - apiVersion: 'v1', - kind: 'ConfigMap', + apiVersion: "v1", + kind: "ConfigMap", metadata: { name: `${serverName}-config`, namespace: namespace, @@ -23,14 +23,14 @@ export async function createServer( app: serverName, [`${LABEL_PREFIX}/server-type`]: server.type.toLowerCase(), [`${LABEL_PREFIX}/server-id`]: server.id, - } + }, }, data: { - 'server-type': server.type, - 'minikura-api-key': server.apiKey, - } + "server-type": server.type, + "minikura-api-key": server.apiKey, + }, }; - + try { await coreApi.createNamespacedConfigMap(namespace, configMap); console.log(`Created ConfigMap for server ${server.id}`); @@ -45,8 +45,8 @@ export async function createServer( } const service = { - apiVersion: 'v1', - kind: 'Service', + apiVersion: "v1", + kind: "Service", metadata: { name: serverName, namespace: namespace, @@ -54,7 +54,7 @@ export async function createServer( app: serverName, [`${LABEL_PREFIX}/server-type`]: server.type.toLowerCase(), [`${LABEL_PREFIX}/server-id`]: server.id, - } + }, }, spec: { selector: { @@ -64,14 +64,14 @@ export async function createServer( { port: server.listen_port, targetPort: 25565, - protocol: 'TCP', - name: 'minecraft', - } + protocol: "TCP", + name: "minecraft", + }, ], - type: 'ClusterIP', // Always ClusterIP for regular servers - } + type: "ClusterIP", // Always ClusterIP for regular servers + }, }; - + try { await coreApi.createNamespacedService(namespace, service); console.log(`Created Service for server ${server.id}`); @@ -99,78 +99,78 @@ async function createDeployment( namespace: string ): Promise { const deployment = { - apiVersion: 'apps/v1', - kind: 'Deployment', + apiVersion: "apps/v1", + kind: "Deployment", metadata: { name: serverName, namespace: namespace, labels: { app: serverName, - [`${LABEL_PREFIX}/server-type`]: 'stateless', + [`${LABEL_PREFIX}/server-type`]: "stateless", [`${LABEL_PREFIX}/server-id`]: server.id, - } + }, }, spec: { replicas: 1, selector: { matchLabels: { app: serverName, - } + }, }, template: { metadata: { labels: { app: serverName, - [`${LABEL_PREFIX}/server-type`]: 'stateless', + [`${LABEL_PREFIX}/server-type`]: "stateless", [`${LABEL_PREFIX}/server-id`]: server.id, - } + }, }, spec: { containers: [ { - name: 'minecraft', - image: 'itzg/minecraft-server', + name: "minecraft", + image: "itzg/minecraft-server", ports: [ { containerPort: 25565, - name: 'minecraft', - } + name: "minecraft", + }, ], env: [ { - name: 'EULA', - value: 'TRUE', + name: "EULA", + value: "TRUE", }, { - name: 'TYPE', - value: 'VANILLA', + name: "TYPE", + value: "VANILLA", }, { - name: 'MEMORY', - value: calculateJavaMemory(server.memory || '1G', 0.8), + name: "MEMORY", + value: calculateJavaMemory(server.memory || "1G", 0.8), }, { - name: 'OPS', - value: '', + name: "OPS", + value: "", }, { - name: 'OVERRIDE_SERVER_PROPERTIES', - value: 'true', + name: "OVERRIDE_SERVER_PROPERTIES", + value: "true", }, { - name: 'ENABLE_RCON', - value: 'false', + name: "ENABLE_RCON", + value: "false", }, - ...(server.env_variables || []).map(ev => ({ + ...(server.env_variables || []).map((ev) => ({ name: ev.key, value: ev.value, })), ], volumeMounts: [ { - name: 'config', - mountPath: '/config', - } + name: "config", + mountPath: "/config", + }, ], readinessProbe: { tcpSocket: { @@ -187,23 +187,23 @@ async function createDeployment( limits: { memory: convertToK8sFormat(server.memory || "1G"), cpu: "500m", - } - } - } + }, + }, + }, ], volumes: [ { - name: 'config', + name: "config", configMap: { name: `${serverName}-config`, - } - } - ] - } - } - } + }, + }, + ], + }, + }, + }, }; - + try { await appsApi.createNamespacedDeployment(namespace, deployment); console.log(`Created Deployment for server ${server.id}`); @@ -225,16 +225,16 @@ async function createStatefulSet( namespace: string ): Promise { const statefulSet = { - apiVersion: 'apps/v1', - kind: 'StatefulSet', + apiVersion: "apps/v1", + kind: "StatefulSet", metadata: { name: serverName, namespace: namespace, labels: { app: serverName, - [`${LABEL_PREFIX}/server-type`]: 'stateful', + [`${LABEL_PREFIX}/server-type`]: "stateful", [`${LABEL_PREFIX}/server-id`]: server.id, - } + }, }, spec: { serviceName: serverName, @@ -242,66 +242,66 @@ async function createStatefulSet( selector: { matchLabels: { app: serverName, - } + }, }, template: { metadata: { labels: { app: serverName, - [`${LABEL_PREFIX}/server-type`]: 'stateful', + [`${LABEL_PREFIX}/server-type`]: "stateful", [`${LABEL_PREFIX}/server-id`]: server.id, - } + }, }, spec: { containers: [ { - name: 'minecraft', - image: 'itzg/minecraft-server', + name: "minecraft", + image: "itzg/minecraft-server", ports: [ { containerPort: 25565, - name: 'minecraft', - } + name: "minecraft", + }, ], env: [ { - name: 'EULA', - value: 'TRUE', + name: "EULA", + value: "TRUE", }, { - name: 'TYPE', - value: 'VANILLA', + name: "TYPE", + value: "VANILLA", }, { - name: 'MEMORY', - value: calculateJavaMemory(server.memory || '1G', 0.8), + name: "MEMORY", + value: calculateJavaMemory(server.memory || "1G", 0.8), }, { - name: 'OPS', - value: '', + name: "OPS", + value: "", }, { - name: 'OVERRIDE_SERVER_PROPERTIES', - value: 'true', + name: "OVERRIDE_SERVER_PROPERTIES", + value: "true", }, { - name: 'ENABLE_RCON', - value: 'false', + name: "ENABLE_RCON", + value: "false", }, - ...(server.env_variables || []).map(ev => ({ + ...(server.env_variables || []).map((ev) => ({ name: ev.key, value: ev.value, })), ], volumeMounts: [ { - name: 'data', - mountPath: '/data', + name: "data", + mountPath: "/data", }, { - name: 'config', - mountPath: '/config', - } + name: "config", + mountPath: "/config", + }, ], readinessProbe: { tcpSocket: { @@ -318,38 +318,38 @@ async function createStatefulSet( limits: { memory: convertToK8sFormat(server.memory), cpu: "500m", - } - } - } + }, + }, + }, ], volumes: [ { - name: 'config', + name: "config", configMap: { name: `${serverName}-config`, - } - } - ] - } + }, + }, + ], + }, }, volumeClaimTemplates: [ { metadata: { - name: 'data', + name: "data", }, spec: { - accessModes: ['ReadWriteOnce'], + accessModes: ["ReadWriteOnce"], resources: { requests: { - storage: '1Gi', - } - } - } - } - ] - } + storage: "1Gi", + }, + }, + }, + }, + ], + }, }; - + try { await appsApi.createNamespacedStatefulSet(namespace, statefulSet); console.log(`Created StatefulSet for server ${server.id}`); @@ -372,7 +372,7 @@ export async function deleteServer( namespace: string ): Promise { const serverName = `minecraft-${serverId2}`; - + try { await appsApi.deleteNamespacedDeployment(serverName, namespace); console.log(`Deleted Deployment for server ${serverName}`); @@ -381,7 +381,7 @@ export async function deleteServer( console.error(`Error deleting Deployment for server ${serverName}:`, err); } } - + try { await appsApi.deleteNamespacedStatefulSet(serverName, namespace); console.log(`Deleted StatefulSet for server ${serverName}`); @@ -390,7 +390,7 @@ export async function deleteServer( console.error(`Error deleting StatefulSet for server ${serverName}:`, err); } } - + try { await coreApi.deleteNamespacedService(serverName, namespace); console.log(`Deleted Service for server ${serverName}`); @@ -399,7 +399,7 @@ export async function deleteServer( console.error(`Error deleting Service for server ${serverName}:`, err); } } - + try { await coreApi.deleteNamespacedConfigMap(`${serverName}-config`, namespace); console.log(`Deleted ConfigMap for server ${serverName}`); @@ -408,4 +408,4 @@ export async function deleteServer( console.error(`Error deleting ConfigMap for server ${serverName}:`, err); } } -} \ No newline at end of file +} diff --git a/packages/k8s-operator/src/scripts/apply-crds.ts b/packages/k8s-operator/src/scripts/apply-crds.ts index f33002d..40f2bcb 100644 --- a/packages/k8s-operator/src/scripts/apply-crds.ts +++ b/packages/k8s-operator/src/scripts/apply-crds.ts @@ -1,29 +1,29 @@ -import { KubernetesClient } from '../utils/k8s-client'; -import { registerRBACResources } from '../utils/rbac-registrar'; -import { setupCRDRegistration } from '../utils/crd-registrar'; -import { NAMESPACE } from '../config/constants'; -import { PrismaClient } from '@minikura/db'; -import { dotenvLoad } from 'dotenv-mono'; +import { KubernetesClient } from "../utils/k8s-client"; +import { registerRBACResources } from "../utils/rbac-registrar"; +import { setupCRDRegistration } from "../utils/crd-registrar"; +import { NAMESPACE } from "../config/constants"; +import { PrismaClient } from "@minikura/db"; +import { dotenvLoad } from "dotenv-mono"; dotenvLoad(); async function main() { - console.log('Starting to apply TypeScript-defined CRDs to Kubernetes cluster...'); - + console.log("Starting to apply TypeScript-defined CRDs to Kubernetes cluster..."); + try { const k8sClient = KubernetesClient.getInstance(); console.log(`Connected to Kubernetes cluster, using namespace: ${NAMESPACE}`); await registerRBACResources(k8sClient); - - console.log('Registering Custom Resource Definitions...'); + + console.log("Registering Custom Resource Definitions..."); const prisma = new PrismaClient(); await setupCRDRegistration(prisma, k8sClient, NAMESPACE); - - console.log('Successfully applied all resources to Kubernetes cluster'); + + console.log("Successfully applied all resources to Kubernetes cluster"); process.exit(0); } catch (error: any) { - console.error('Failed to apply resources:', error.message); + console.error("Failed to apply resources:", error.message); if (error.stack) { console.error(error.stack); } @@ -31,7 +31,7 @@ async function main() { } } -main().catch(error => { - console.error('Unhandled error:', error); +main().catch((error) => { + console.error("Unhandled error:", error); process.exit(1); -}); \ No newline at end of file +}); diff --git a/packages/k8s-operator/src/types/index.ts b/packages/k8s-operator/src/types/index.ts index 605f659..297ed46 100644 --- a/packages/k8s-operator/src/types/index.ts +++ b/packages/k8s-operator/src/types/index.ts @@ -1,10 +1,10 @@ -import type { - ServerType, - ReverseProxyServerType, - Server as PrismaServer, +import type { + ServerType, + ReverseProxyServerType, + Server as PrismaServer, ReverseProxyServer as PrismaReverseProxyServer, - CustomEnvironmentVariable -} from '@minikura/db'; + CustomEnvironmentVariable, +} from "@minikura/db"; // Base interface export interface CustomResource { @@ -19,17 +19,23 @@ export interface CustomResource { }; } -export type ServerConfig = Pick & { +export type ServerConfig = Pick< + PrismaServer, + "id" | "description" | "type" | "listen_port" | "memory" +> & { apiKey: string; - env_variables?: Array>; + env_variables?: Array>; }; -export type MinecraftServerSpec = Pick & { - environmentVariables?: Array>; +export type MinecraftServerSpec = Pick< + PrismaServer, + "id" | "description" | "type" | "listen_port" | "memory" +> & { + environmentVariables?: Array>; }; export interface MinecraftServerStatus { - phase: 'Pending' | 'Running' | 'Failed'; + phase: "Pending" | "Running" | "Failed"; message?: string; apiKey?: string; internalId?: string; @@ -44,24 +50,27 @@ export interface MinecraftServerCRD extends CustomResource { // Reverse Proxy Types export type ReverseProxyConfig = Pick< - PrismaReverseProxyServer, - 'id' | 'description' | 'external_address' | 'external_port' | 'listen_port' | 'type' | 'memory' + PrismaReverseProxyServer, + "id" | "description" | "external_address" | "external_port" | "listen_port" | "type" | "memory" > & { apiKey: string; - env_variables?: Array>; + env_variables?: Array>; }; export type ReverseProxyServerSpec = Partial< - Pick + Pick< + PrismaReverseProxyServer, + "id" | "description" | "external_address" | "external_port" | "listen_port" | "type" | "memory" + > > & { id: string; external_address: string; external_port: number; - environmentVariables?: Array>; + environmentVariables?: Array>; }; export interface ReverseProxyServerStatus { - phase: 'Pending' | 'Running' | 'Failed'; + phase: "Pending" | "Running" | "Failed"; message?: string; apiKey?: string; internalId?: string; @@ -73,4 +82,4 @@ export interface ReverseProxyServerCRD extends CustomResource { status?: ReverseProxyServerStatus; } -export type EnvironmentVariable = Pick; \ No newline at end of file +export type EnvironmentVariable = Pick; diff --git a/packages/k8s-operator/src/utils/crd-registrar.ts b/packages/k8s-operator/src/utils/crd-registrar.ts index ecbfe59..bc55aaf 100644 --- a/packages/k8s-operator/src/utils/crd-registrar.ts +++ b/packages/k8s-operator/src/utils/crd-registrar.ts @@ -1,9 +1,9 @@ -import { PrismaClient } from '@minikura/db'; -import type { Server, ReverseProxyServer, CustomEnvironmentVariable } from '@minikura/db'; -import { KubernetesClient } from './k8s-client'; -import { API_GROUP, API_VERSION, LABEL_PREFIX } from '../config/constants'; -import { MINECRAFT_SERVER_CRD } from '../crds/server'; -import { REVERSE_PROXY_SERVER_CRD } from '../crds/reverseProxy'; +import type { PrismaClient } from "@minikura/db"; +import type { Server, ReverseProxyServer, CustomEnvironmentVariable } from "@minikura/db"; +import type { KubernetesClient } from "./k8s-client"; +import { API_GROUP, API_VERSION, LABEL_PREFIX } from "../config/constants"; +import { MINECRAFT_SERVER_CRD } from "../crds/server"; +import { REVERSE_PROXY_SERVER_CRD } from "../crds/reverseProxy"; /** * Sets up the CRD registration and starts a reflector to sync database state to CRDs @@ -24,7 +24,7 @@ async function registerCRDs(k8sClient: KubernetesClient): Promise { try { const apiExtensionsClient = k8sClient.getApiExtensionsApi(); - console.log('Registering CRDs...'); + console.log("Registering CRDs..."); try { await apiExtensionsClient.createCustomResourceDefinition(MINECRAFT_SERVER_CRD); @@ -32,9 +32,9 @@ async function registerCRDs(k8sClient: KubernetesClient): Promise { } catch (error: any) { if (error.response?.statusCode === 409) { // TODO: Handle conflict - console.log('MinecraftServer CRD already exists'); + console.log("MinecraftServer CRD already exists"); } else { - console.error('Error creating MinecraftServer CRD:', error); + console.error("Error creating MinecraftServer CRD:", error); } } @@ -43,14 +43,14 @@ async function registerCRDs(k8sClient: KubernetesClient): Promise { console.log(`ReverseProxyServer CRD created successfully (${API_GROUP}/${API_VERSION})`); } catch (error: any) { if (error.response?.statusCode === 409) { - // TODO: Handle conflict - console.log('ReverseProxyServer CRD already exists'); + // TODO: Handle conflict + console.log("ReverseProxyServer CRD already exists"); } else { - console.error('Error creating ReverseProxyServer CRD:', error); + console.error("Error creating ReverseProxyServer CRD:", error); } } } catch (error) { - console.error('Error registering CRDs:', error); + console.error("Error registering CRDs:", error); throw error; } } @@ -69,17 +69,27 @@ async function startCRDReflector( const reflectedMinecraftServers = new Map(); // DB ID -> CR name const reflectedReverseProxyServers = new Map(); // DB ID -> CR name - console.log('Starting CRD reflector...'); - + console.log("Starting CRD reflector..."); + // Initial sync to create CRs that reflect the DB state - await syncDBtoCRDs(prisma, customObjectsApi, namespace, - reflectedMinecraftServers, reflectedReverseProxyServers); - + await syncDBtoCRDs( + prisma, + customObjectsApi, + namespace, + reflectedMinecraftServers, + reflectedReverseProxyServers + ); + // Polling interval to check for changes in the DB // TODO: Make this listener instead setInterval(async () => { - await syncDBtoCRDs(prisma, customObjectsApi, namespace, - reflectedMinecraftServers, reflectedReverseProxyServers); + await syncDBtoCRDs( + prisma, + customObjectsApi, + namespace, + reflectedMinecraftServers, + reflectedReverseProxyServers + ); }, 30 * 1000); } @@ -96,7 +106,12 @@ async function syncDBtoCRDs( try { console.log(`[${new Date().toISOString()}] Starting CRD sync operation...`); await syncMinecraftServers(prisma, customObjectsApi, namespace, reflectedMinecraftServers); - await syncReverseProxyServers(prisma, customObjectsApi, namespace, reflectedReverseProxyServers); + await syncReverseProxyServers( + prisma, + customObjectsApi, + namespace, + reflectedReverseProxyServers + ); console.log(`[${new Date().toISOString()}] CRD sync operation completed`); } catch (error) { console.error(`[${new Date().toISOString()}] Error syncing database to CRDs:`, error); @@ -114,18 +129,18 @@ async function syncMinecraftServers( ): Promise { try { const servers = await prisma.server.findMany(); - + let existingCRs: any[] = []; try { const response = await customObjectsApi.listNamespacedCustomObject( API_GROUP, API_VERSION, namespace, - 'minecraftservers' + "minecraftservers" ); existingCRs = (response.body as any).items || []; } catch (error) { - console.error('Error listing MinecraftServer CRs:', error); + console.error("Error listing MinecraftServer CRs:", error); // TODO: Potentially better error handling here // For now, continue anyway - it might just be that none exist yet } @@ -134,7 +149,7 @@ async function syncMinecraftServers( const existingCRMap = new Map(); // Map of CR names to their resourceVersions for updates const crResourceVersions = new Map(); - + for (const cr of existingCRs) { const internalId = cr.status?.internalId; if (internalId) { @@ -148,48 +163,48 @@ async function syncMinecraftServers( // Refresh tracking map reflectedMinecraftServers.clear(); - + // Create or update CRs for each server for (const server of servers) { const crName = existingCRMap.get(server.id) || `${server.id.toLowerCase()}`; - + // Build the CR object const serverCR: { - apiVersion: string, - kind: string, + apiVersion: string; + kind: string; metadata: { - name: string, - namespace: string, - annotations: Record, - resourceVersion?: string - }, - spec: any, - status: any + name: string; + namespace: string; + annotations: Record; + resourceVersion?: string; + }; + spec: any; + status: any; } = { apiVersion: `${API_GROUP}/${API_VERSION}`, - kind: 'MinecraftServer', + kind: "MinecraftServer", metadata: { name: crName, namespace: namespace, annotations: { - [`${LABEL_PREFIX}/database-managed`]: 'true', - [`${LABEL_PREFIX}/last-synced`]: new Date().toISOString() - } + [`${LABEL_PREFIX}/database-managed`]: "true", + [`${LABEL_PREFIX}/last-synced`]: new Date().toISOString(), + }, }, spec: { id: server.id, description: server.description, listen_port: server.listen_port, type: server.type, - memory: server.memory + memory: server.memory, }, status: { - phase: 'Running', - message: 'Managed by database', + phase: "Running", + message: "Managed by database", internalId: server.id, - apiKey: '[REDACTED]', // Don't expose actual API key - lastSyncedAt: new Date().toISOString() - } + apiKey: "[REDACTED]", // Don't expose actual API key + lastSyncedAt: new Date().toISOString(), + }, }; try { @@ -198,29 +213,29 @@ async function syncMinecraftServers( // Get the current resource first const crName = existingCRMap.get(server.id)!; - + try { // Get the existing resource to get the current resourceVersion const existingResource = await customObjectsApi.getNamespacedCustomObject( API_GROUP, API_VERSION, namespace, - 'minecraftservers', + "minecraftservers", crName ); - + // Extract the resourceVersion from the existing resource if (existingResource?.body?.metadata?.resourceVersion) { // Add the resourceVersion to our custom resource serverCR.metadata.resourceVersion = existingResource.body.metadata.resourceVersion; } - + // Now update with the correct resourceVersion await customObjectsApi.replaceNamespacedCustomObject( API_GROUP, API_VERSION, namespace, - 'minecraftservers', + "minecraftservers", crName, serverCR ); @@ -234,28 +249,28 @@ async function syncMinecraftServers( API_GROUP, API_VERSION, namespace, - 'minecraftservers', + "minecraftservers", serverCR ); console.log(`Created MinecraftServer CR ${crName} for server ${server.id}`); } - + // Remember this mapping reflectedMinecraftServers.set(server.id, crName); } catch (error) { console.error(`Error creating/updating MinecraftServer CR for ${server.id}:`, error); } } - + // Delete CRs for servers that no longer exist for (const [dbId, crName] of existingCRMap.entries()) { - if (!servers.some(s => s.id === dbId)) { + if (!servers.some((s) => s.id === dbId)) { try { await customObjectsApi.deleteNamespacedCustomObject( API_GROUP, API_VERSION, namespace, - 'minecraftservers', + "minecraftservers", crName ); console.log(`Deleted MinecraftServer CR ${crName} for removed server ID ${dbId}`); @@ -265,7 +280,7 @@ async function syncMinecraftServers( } } } catch (error) { - console.error('Error syncing Minecraft servers to CRDs:', error); + console.error("Error syncing Minecraft servers to CRDs:", error); } } @@ -281,21 +296,21 @@ async function syncReverseProxyServers( try { const proxies = await prisma.reverseProxyServer.findMany({ include: { - env_variables: true - } + env_variables: true, + }, }); - + let existingCRs: any[] = []; try { const response = await customObjectsApi.listNamespacedCustomObject( API_GROUP, API_VERSION, namespace, - 'reverseproxyservers' + "reverseproxyservers" ); existingCRs = (response.body as any).items || []; } catch (error) { - console.error('Error listing ReverseProxyServer CRs:', error); + console.error("Error listing ReverseProxyServer CRs:", error); // TODO: Potentially better error handling here // For now, continue anyway - it might just be that none exist yet } @@ -304,7 +319,7 @@ async function syncReverseProxyServers( const existingCRMap = new Map(); // Map of CR names to their resourceVersions for updates const crResourceVersions = new Map(); - + for (const cr of existingCRs) { const internalId = cr.status?.internalId; if (internalId) { @@ -318,33 +333,33 @@ async function syncReverseProxyServers( // Refresh tracking map reflectedReverseProxyServers.clear(); - + // Create or update CRs for each proxy for (const proxy of proxies) { const crName = existingCRMap.get(proxy.id) || `${proxy.id.toLowerCase()}`; - + // Build the CR object const proxyCR: { - apiVersion: string, - kind: string, + apiVersion: string; + kind: string; metadata: { - name: string, - namespace: string, - annotations: Record, - resourceVersion?: string - }, - spec: any, - status: any + name: string; + namespace: string; + annotations: Record; + resourceVersion?: string; + }; + spec: any; + status: any; } = { apiVersion: `${API_GROUP}/${API_VERSION}`, - kind: 'ReverseProxyServer', + kind: "ReverseProxyServer", metadata: { name: crName, namespace: namespace, annotations: { - [`${LABEL_PREFIX}/database-managed`]: 'true', - [`${LABEL_PREFIX}/last-synced`]: new Date().toISOString() - } + [`${LABEL_PREFIX}/database-managed`]: "true", + [`${LABEL_PREFIX}/last-synced`]: new Date().toISOString(), + }, }, spec: { id: proxy.id, @@ -354,18 +369,18 @@ async function syncReverseProxyServers( listen_port: proxy.listen_port, type: proxy.type, memory: proxy.memory, - environmentVariables: proxy.env_variables?.map(ev => ({ + environmentVariables: proxy.env_variables?.map((ev) => ({ key: ev.key, - value: ev.value - })) + value: ev.value, + })), }, status: { - phase: 'Running', - message: 'Managed by database', + phase: "Running", + message: "Managed by database", internalId: proxy.id, - apiKey: '[REDACTED]', // Don't expose actual API key - lastSyncedAt: new Date().toISOString() - } + apiKey: "[REDACTED]", // Don't expose actual API key + lastSyncedAt: new Date().toISOString(), + }, }; try { @@ -374,29 +389,29 @@ async function syncReverseProxyServers( // Get the current resource first const crName = existingCRMap.get(proxy.id)!; - + try { // Get the existing resource to get the current resourceVersion const existingResource = await customObjectsApi.getNamespacedCustomObject( API_GROUP, API_VERSION, namespace, - 'reverseproxyservers', + "reverseproxyservers", crName ); - + // Extract the resourceVersion from the existing resource if (existingResource?.body?.metadata?.resourceVersion) { // Add the resourceVersion to our custom resource proxyCR.metadata.resourceVersion = existingResource.body.metadata.resourceVersion; } - + // Now update with the correct resourceVersion await customObjectsApi.replaceNamespacedCustomObject( API_GROUP, API_VERSION, namespace, - 'reverseproxyservers', + "reverseproxyservers", crName, proxyCR ); @@ -410,28 +425,28 @@ async function syncReverseProxyServers( API_GROUP, API_VERSION, namespace, - 'reverseproxyservers', + "reverseproxyservers", proxyCR ); console.log(`Created ReverseProxyServer CR ${crName} for proxy ${proxy.id}`); } - + // Remember this mapping reflectedReverseProxyServers.set(proxy.id, crName); } catch (error) { console.error(`Error creating/updating ReverseProxyServer CR for ${proxy.id}:`, error); } } - + // Delete CRs for proxies that no longer exist for (const [dbId, crName] of existingCRMap.entries()) { - if (!proxies.some(p => p.id === dbId)) { + if (!proxies.some((p) => p.id === dbId)) { try { await customObjectsApi.deleteNamespacedCustomObject( API_GROUP, API_VERSION, namespace, - 'reverseproxyservers', + "reverseproxyservers", crName ); console.log(`Deleted ReverseProxyServer CR ${crName} for removed proxy ID ${dbId}`); @@ -441,6 +456,6 @@ async function syncReverseProxyServers( } } } catch (error) { - console.error('Error syncing Reverse Proxy servers to CRDs:', error); + console.error("Error syncing Reverse Proxy servers to CRDs:", error); } -} \ No newline at end of file +} diff --git a/packages/k8s-operator/src/utils/k8s-client.ts b/packages/k8s-operator/src/utils/k8s-client.ts index 2fc7aba..969e4a9 100644 --- a/packages/k8s-operator/src/utils/k8s-client.ts +++ b/packages/k8s-operator/src/utils/k8s-client.ts @@ -1,5 +1,5 @@ -import * as k8s from '@kubernetes/client-node'; -import { SKIP_TLS_VERIFY, NAMESPACE } from '../config/constants'; +import * as k8s from "@kubernetes/client-node"; +import { SKIP_TLS_VERIFY, NAMESPACE } from "../config/constants"; export class KubernetesClient { private static instance: KubernetesClient; @@ -12,10 +12,10 @@ export class KubernetesClient { private constructor() { if (SKIP_TLS_VERIFY) { - console.log('Disabling TLS certificate validation'); - process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + console.log("Disabling TLS certificate validation"); + process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; } - + this.kc = new k8s.KubeConfig(); this.setupConfig(); this.initializeClients(); @@ -31,23 +31,23 @@ export class KubernetesClient { private setupConfig(): void { try { this.kc.loadFromDefault(); - console.log('Loaded Kubernetes config from default location'); + console.log("Loaded Kubernetes config from default location"); } catch (err) { - console.warn('Failed to load Kubernetes config from default location:', err); + console.warn("Failed to load Kubernetes config from default location:", err); } // Running in a cluster, try to load in-cluster config if (!this.kc.getCurrentContext()) { try { this.kc.loadFromCluster(); - console.log('Loaded Kubernetes config from cluster'); + console.log("Loaded Kubernetes config from cluster"); } catch (err) { - console.warn('Failed to load Kubernetes config from cluster:', err); + console.warn("Failed to load Kubernetes config from cluster:", err); } } if (!this.kc.getCurrentContext()) { - throw new Error('Failed to setup Kubernetes client - no valid configuration found'); + throw new Error("Failed to setup Kubernetes client - no valid configuration found"); } const currentCluster = this.kc.getCurrentCluster(); @@ -90,12 +90,12 @@ export class KubernetesClient { async handleApiError(error: any, context: string): Promise { console.error(`Kubernetes API error (${context}):`, error?.message || error); - + if (error?.response) { console.error(`Response status: ${error.response.statusCode}`); console.error(`Response body: ${JSON.stringify(error.response.body)}`); } - + throw error; } -} \ No newline at end of file +} diff --git a/packages/k8s-operator/src/utils/memory.ts b/packages/k8s-operator/src/utils/memory.ts index 9a69ad9..1db6b60 100644 --- a/packages/k8s-operator/src/utils/memory.ts +++ b/packages/k8s-operator/src/utils/memory.ts @@ -11,10 +11,10 @@ export function calculateJavaMemory(memoryString: string, factor: number): string { const match = memoryString.match(/^(\d+)([MG])$/i); if (!match) return "512M"; // Default if format is not recognized - + const [, valueStr, unit] = match; const value = parseInt(valueStr, 10); - + const calculatedValue = Math.round(value * factor); return `${calculatedValue}${unit.toUpperCase()}`; } @@ -27,12 +27,12 @@ export function calculateJavaMemory(memoryString: string, factor: number): strin export function convertToK8sFormat(memoryString: string): string { const match = memoryString.match(/^(\d+)([MG])$/i); if (!match) return "1Gi"; // Default if format is not recognized - + const [, valueStr, unit] = match; - - if (unit.toUpperCase() === 'G') { + + if (unit.toUpperCase() === "G") { return `${valueStr}Gi`; } else { return `${valueStr}Mi`; } -} \ No newline at end of file +} diff --git a/packages/k8s-operator/src/utils/rbac-registrar.ts b/packages/k8s-operator/src/utils/rbac-registrar.ts index 8f2387c..dbb2394 100644 --- a/packages/k8s-operator/src/utils/rbac-registrar.ts +++ b/packages/k8s-operator/src/utils/rbac-registrar.ts @@ -1,12 +1,12 @@ -import { KubernetesClient } from './k8s-client'; -import { - minikuraNamespace, - minikuraServiceAccount, - minikuraClusterRole, +import type { KubernetesClient } from "./k8s-client"; +import { + minikuraNamespace, + minikuraServiceAccount, + minikuraClusterRole, minikuraClusterRoleBinding, - minikuraOperatorDeployment -} from '../crds/rbac'; -import fetch from 'node-fetch'; + minikuraOperatorDeployment, +} from "../crds/rbac"; +import fetch from "node-fetch"; /** * Registers all RBAC resources required @@ -14,16 +14,16 @@ import fetch from 'node-fetch'; */ export async function registerRBACResources(k8sClient: KubernetesClient): Promise { try { - console.log('Starting RBAC resources registration...'); - + console.log("Starting RBAC resources registration..."); + await registerNamespace(k8sClient); await registerServiceAccount(k8sClient); await registerClusterRole(k8sClient); await registerClusterRoleBinding(k8sClient); - - console.log('RBAC resources registration completed successfully'); + + console.log("RBAC resources registration completed successfully"); } catch (error: any) { - console.error('Error registering RBAC resources:', error.message); + console.error("Error registering RBAC resources:", error.message); if (error.response) { console.error(`Response status: ${error.response.statusCode}`); console.error(`Response body: ${JSON.stringify(error.response.body)}`); @@ -78,35 +78,40 @@ async function registerClusterRole(k8sClient: KubernetesClient): Promise { const kc = k8sClient.getKubeConfig(); const opts = {}; kc.applyToRequest(opts as any); - + // Get cluster URL const cluster = kc.getCurrentCluster(); if (!cluster) { - throw new Error('No active cluster found in KubeConfig'); + throw new Error("No active cluster found in KubeConfig"); } - + try { - const response = await fetch(`${cluster.server}/apis/rbac.authorization.k8s.io/v1/clusterroles`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - ...(opts as any).headers - }, - body: JSON.stringify(minikuraClusterRole), - agent: (opts as any).agent - }); - + const response = await fetch( + `${cluster.server}/apis/rbac.authorization.k8s.io/v1/clusterroles`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + ...(opts as any).headers, + }, + body: JSON.stringify(minikuraClusterRole), + agent: (opts as any).agent, + } + ); + if (response.ok) { console.log(`Created cluster role ${minikuraClusterRole.metadata.name}`); } else if (response.status === 409) { console.log(`Cluster role ${minikuraClusterRole.metadata.name} already exists`); } else { const text = await response.text(); - throw new Error(`Failed to create cluster role: ${response.status} ${response.statusText} - ${text}`); + throw new Error( + `Failed to create cluster role: ${response.status} ${response.statusText} - ${text}` + ); } } catch (error: any) { // If the error message contains "already exists", that's OK - if (error.message?.includes('already exists') || error.message?.includes('409')) { + if (error.message?.includes("already exists") || error.message?.includes("409")) { console.log(`Cluster role ${minikuraClusterRole.metadata.name} already exists`); } else { throw error; @@ -127,40 +132,49 @@ async function registerClusterRoleBinding(k8sClient: KubernetesClient): Promise< const kc = k8sClient.getKubeConfig(); const opts = {}; kc.applyToRequest(opts as any); - + // Get cluster URL const cluster = kc.getCurrentCluster(); if (!cluster) { - throw new Error('No active cluster found in KubeConfig'); + throw new Error("No active cluster found in KubeConfig"); } - + // Create the cluster role binding - const { default: fetch } = await import('node-fetch'); - + const { default: fetch } = await import("node-fetch"); + try { - const response = await fetch(`${cluster.server}/apis/rbac.authorization.k8s.io/v1/clusterrolebindings`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - ...(opts as any).headers - }, - body: JSON.stringify(minikuraClusterRoleBinding), - agent: (opts as any).agent - }); - + const response = await fetch( + `${cluster.server}/apis/rbac.authorization.k8s.io/v1/clusterrolebindings`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + ...(opts as any).headers, + }, + body: JSON.stringify(minikuraClusterRoleBinding), + agent: (opts as any).agent, + } + ); + if (response.ok) { console.log(`Created cluster role binding ${minikuraClusterRoleBinding.metadata.name}`); } else if (response.status === 409) { - console.log(`Cluster role binding ${minikuraClusterRoleBinding.metadata.name} already exists`); + console.log( + `Cluster role binding ${minikuraClusterRoleBinding.metadata.name} already exists` + ); } else { const text = await response.text(); - throw new Error(`Failed to create cluster role binding: ${response.status} ${response.statusText} - ${text}`); + throw new Error( + `Failed to create cluster role binding: ${response.status} ${response.statusText} - ${text}` + ); } } catch (error: any) { // If the error message contains "already exists" // TODO: Potentially better error handling here - if (error.message?.includes('already exists') || error.message?.includes('409')) { - console.log(`Cluster role binding ${minikuraClusterRoleBinding.metadata.name} already exists`); + if (error.message?.includes("already exists") || error.message?.includes("409")) { + console.log( + `Cluster role binding ${minikuraClusterRoleBinding.metadata.name} already exists` + ); } else { throw error; } @@ -176,37 +190,36 @@ async function registerClusterRoleBinding(k8sClient: KubernetesClient): Promise< * Note: This requires the secret to be created first */ export async function registerOperatorDeployment( - k8sClient: KubernetesClient, + k8sClient: KubernetesClient, registryUrl: string ): Promise { try { // Replace the registry URL placeholder, for future use const deployment = JSON.parse( - JSON.stringify(minikuraOperatorDeployment).replace('${REGISTRY_URL}', registryUrl) + JSON.stringify(minikuraOperatorDeployment).replace("${REGISTRY_URL}", registryUrl) ); - + const appsApi = k8sClient.getAppsApi(); - await appsApi.createNamespacedDeployment( - deployment.metadata.namespace, - deployment - ); + await appsApi.createNamespacedDeployment(deployment.metadata.namespace, deployment); console.log(`Created deployment ${deployment.metadata.name}`); } catch (error: any) { if (error.response?.statusCode === 409) { console.log(`Deployment ${minikuraOperatorDeployment.metadata.name} already exists`); // Update the deployment if it already exists const deployment = JSON.parse( - JSON.stringify(minikuraOperatorDeployment).replace('${REGISTRY_URL}', registryUrl) - ); - - await k8sClient.getAppsApi().replaceNamespacedDeployment( - deployment.metadata.name, - deployment.metadata.namespace, - deployment + JSON.stringify(minikuraOperatorDeployment).replace("${REGISTRY_URL}", registryUrl) ); + + await k8sClient + .getAppsApi() + .replaceNamespacedDeployment( + deployment.metadata.name, + deployment.metadata.namespace, + deployment + ); console.log(`Updated deployment ${deployment.metadata.name}`); } else { throw error; } } -} \ No newline at end of file +}