From 91587f66b26412bb69eea3d1552d84de5ca4f42a Mon Sep 17 00:00:00 2001 From: Dunemask Date: Fri, 22 Dec 2023 14:45:49 -0700 Subject: [PATCH] [REV] Switch to use IDS over server names --- lib/controllers/file-controller.js | 10 +-- lib/controllers/lifecycle-controller.js | 34 +++++----- lib/database/queries/server-queries.js | 37 +++++++--- lib/k8s/configs/rcon-secret.yml | 2 +- lib/k8s/configs/rcon-svc.yml | 2 +- lib/k8s/configs/server-deployment.yml | 4 +- lib/k8s/configs/server-pvc.yml | 2 +- lib/k8s/configs/server-svc.yml | 2 +- lib/k8s/k8s-server-control.js | 58 ++++++---------- lib/k8s/server-containers.js | 14 ++-- lib/k8s/server-control.js | 83 +++++++++-------------- lib/k8s/server-create.js | 80 ++++++++++------------ lib/k8s/server-delete.js | 4 +- lib/k8s/server-files.js | 4 +- src/components/files/MineclusterFiles.jsx | 12 ++-- src/components/servers/RconDialog.jsx | 5 +- src/components/servers/RconSocket.js | 4 +- src/components/servers/RconView.jsx | 4 +- src/components/servers/ServerCard.jsx | 12 ++-- src/pages/Home.jsx | 4 +- src/util/queries.js | 40 +++++------ 21 files changed, 196 insertions(+), 221 deletions(-) diff --git a/lib/controllers/file-controller.js b/lib/controllers/file-controller.js index 7af1a4c..1a92c17 100644 --- a/lib/controllers/file-controller.js +++ b/lib/controllers/file-controller.js @@ -10,7 +10,7 @@ import { sendError } from "../util/ExpressClientError.js"; export async function listFiles(req, res) { const serverSpec = req.body; if (!serverSpec) return res.sendStatus(400); - if (!serverSpec.host) return res.status(400).send("Server name required!"); + if (!serverSpec.id) return res.status(400).send("Server id missing!"); listServerFiles(serverSpec) .then((f) => { const fileData = f.map((fi, i) => ({ @@ -29,7 +29,7 @@ export async function listFiles(req, res) { export async function createFolder(req, res) { const serverSpec = req.body; if (!serverSpec) return res.sendStatus(400); - if (!serverSpec.host) return res.status(400).send("Server name required!"); + if (!serverSpec.id) return res.status(400).send("Server id missing!"); if (!serverSpec.path) return res.status(400).send("Path required!"); createServerFolder(serverSpec) .then(() => res.sendStatus(200)) @@ -39,7 +39,7 @@ export async function createFolder(req, res) { export async function deleteItem(req, res) { const serverSpec = req.body; if (!serverSpec) return res.sendStatus(400); - if (!serverSpec.host) return res.status(400).send("Server name required!"); + if (!serverSpec.id) return res.status(400).send("Server id missing!"); if (!serverSpec.path) return res.status(400).send("Path required!"); if (serverSpec.isDir === undefined || serverSpec.isDir === null) return res.status(400).send("IsDIr required!"); @@ -50,7 +50,7 @@ export async function deleteItem(req, res) { export async function uploadItem(req, res) { const serverSpec = req.body; - if (!serverSpec.host) return res.status(400).send("Server name required!"); + if (!serverSpec.id) return res.status(400).send("Server id missing!"); if (!serverSpec.path) return res.status(400).send("Path required!"); uploadServerItem(serverSpec, req.file) .then(() => res.sendStatus(200)) @@ -59,7 +59,7 @@ export async function uploadItem(req, res) { export async function getItem(req, res) { const serverSpec = req.body; - if (!serverSpec.host) return res.status(400).send("Server name required!"); + if (!serverSpec.id) return res.status(400).send("Server id missing!"); if (!serverSpec.path) return res.status(400).send("Path required!"); getServerItem(serverSpec, res) .then(({ ds, ftpTransfer }) => { diff --git a/lib/controllers/lifecycle-controller.js b/lib/controllers/lifecycle-controller.js index c0d0316..d0c4289 100644 --- a/lib/controllers/lifecycle-controller.js +++ b/lib/controllers/lifecycle-controller.js @@ -8,33 +8,35 @@ import { import { sendError } from "../util/ExpressClientError.js"; import { toggleServer } from "../k8s/k8s-server-control.js"; +const dnsRegex = new RegExp( + `^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$`, +); + function payloadFilter(req, res) { const serverSpec = req.body; if (!serverSpec) return res.sendStatus(400); - const { name, host, version, serverType, memory } = - serverSpec; + const { name, host, version, serverType, memory } = serverSpec; if (!name) return res.status(400).send("Server name is required!"); if (!host) return res.status(400).send("Server host is required!"); + if (!dnsRegex.test(host)) return res.status(400).send("Hostname invalid!"); if (!version) return res.status(400).send("Server version is required!"); if (!serverType) return res.status(400).send("Server type is required!"); if (!memory) return res.status(400).send("Memory is required!"); return "filtered"; } -function checkServerHost(serverSpec) { +function checkServerId(serverSpec) { if (!serverSpec) throw new ExpressClientError({ c: 400 }); - if (!serverSpec.host) - throw new ExpressClientError({ c: 400, m: "Server name required!" }); + if (!serverSpec.id) + throw new ExpressClientError({ c: 400, m: "Server id missing!" }); } export async function createServer(req, res) { if (payloadFilter(req, res) !== "filtered") return; const serverSpec = req.body; try { - const serverSpecs = await getServerEntry(serverSpec.id); - if (serverSpecs.length !== 0) throw Error("Server already exists in DB!"); - await createServerResources(serverSpec); - await createServerEntry(serverSpec); + const serverEntry = await createServerEntry(serverSpec); + await createServerResources(serverEntry); res.sendStatus(200); } catch (e) { sendError(res)(e); @@ -45,7 +47,7 @@ export async function deleteServer(req, res) { // Ensure spec is safe const serverSpec = req.body; try { - checkServerHost(serverSpec); + checkServerId(serverSpec); } catch (e) { return sendError(res)(e); } @@ -60,12 +62,12 @@ export async function startServer(req, res) { // Ensure spec is safe const serverSpec = req.body; try { - checkServerHost(serverSpec); + checkServerId(serverSpec); } catch (e) { return sendError(res)(e); } - const { name } = serverSpec; - toggleServer(name, true) + const { id } = serverSpec; + toggleServer(id, true) .then(() => res.sendStatus(200)) .catch(sendError(res)); } @@ -74,12 +76,12 @@ export async function stopServer(req, res) { // Ensure spec is safe const serverSpec = req.body; try { - checkServerHost(serverSpec); + checkServerId(serverSpec); } catch (e) { return sendError(res)(e); } - const { name } = serverSpec; - toggleServer(name, false) + const { id } = serverSpec; + toggleServer(id, false) .then(() => res.sendStatus(200)) .catch(sendError(res)); } diff --git a/lib/database/queries/server-queries.js b/lib/database/queries/server-queries.js index d8baa8f..b49acdd 100644 --- a/lib/database/queries/server-queries.js +++ b/lib/database/queries/server-queries.js @@ -9,32 +9,47 @@ const asExpressClientError = (e) => { export async function createServerEntry(serverSpec) { const { name, host, version, serverType: server_type, memory } = serverSpec; - const q = insertQuery(table, { name, host, version, server_type, memory }); + var q = insertQuery(table, { name, host, version, server_type, memory }); + q += "\n RETURNING *"; + try { + const entries = await pg.query(q); + const { + id, + name, + host, + version, + server_type: serverType, + memory, + } = entries[0]; + return { name, id, host, version, serverType, memory }; + } catch (e) { + asExpressClientError(e); + } +} + +export async function deleteServerEntry(serverId) { + if (!serverId) asExpressClientError({ message: "Server ID Required!" }); + const q = deleteQuery(table, { id: serverId }); return pg.query(q).catch(asExpressClientError); } -export async function deleteServerEntry(serverName) { - if (!serverName) asExpressClientError({ message: "Server Name Required!" }); - const q = deleteQuery(table, { name: serverName }); - return pg.query(q).catch(asExpressClientError); -} - -export async function getServerEntry(serverName) { - if (!serverName) asExpressClientError({ message: "Server Name Required!" }); - const q = selectWhereQuery(table, { name: serverName }); +export async function getServerEntry(serverId) { + if (!serverId) asExpressClientError({ message: "Server ID Required!" }); + const q = selectWhereQuery(table, { id: serverId }); try { const serverSpecs = await pg.query(q); if (serverSpecs.length === 0) return []; if (!serverSpecs.length === 1) throw Error("Multiple servers found with the same name!"); const { + id, name, host, version, server_type: serverType, memory, } = serverSpecs[0]; - return { name, host, version, serverType, memory }; + return { name, id, host, version, serverType, memory }; } catch (e) { asExpressClientError(e); } diff --git a/lib/k8s/configs/rcon-secret.yml b/lib/k8s/configs/rcon-secret.yml index ff52e1a..a90c319 100644 --- a/lib/k8s/configs/rcon-secret.yml +++ b/lib/k8s/configs/rcon-secret.yml @@ -4,7 +4,7 @@ data: kind: Secret metadata: annotations: - minecluster.dunemask.net/server-name: changeme-server-name + minecluster.dunemask.net/id: changeme-server-id labels: app: changeme-app-label name: changeme-rcon-secret diff --git a/lib/k8s/configs/rcon-svc.yml b/lib/k8s/configs/rcon-svc.yml index 9a68813..49a7089 100644 --- a/lib/k8s/configs/rcon-svc.yml +++ b/lib/k8s/configs/rcon-svc.yml @@ -2,7 +2,7 @@ apiVersion: v1 kind: Service metadata: annotations: - minecluster.dunemask.net/server-name: changeme-server-name + minecluster.dunemask.net/id: changeme-server-id labels: app: changeme-app name: changeme-rcon diff --git a/lib/k8s/configs/server-deployment.yml b/lib/k8s/configs/server-deployment.yml index 0d5d335..aa31a97 100644 --- a/lib/k8s/configs/server-deployment.yml +++ b/lib/k8s/configs/server-deployment.yml @@ -2,7 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: annotations: - minecluster.dunemask.net/server-name: changeme-server-name + minecluster.dunemask.net/id: changeme-server-id name: changeme-name namespace: changeme-namespace spec: @@ -17,7 +17,7 @@ spec: template: metadata: annotations: - minecluster.dunemask.net/server-name: changeme-server-name + minecluster.dunemask.net/id: changeme-server-id labels: app: changeme-app spec: diff --git a/lib/k8s/configs/server-pvc.yml b/lib/k8s/configs/server-pvc.yml index f502e8a..bf21ea4 100644 --- a/lib/k8s/configs/server-pvc.yml +++ b/lib/k8s/configs/server-pvc.yml @@ -2,7 +2,7 @@ apiVersion: v1 kind: PersistentVolumeClaim metadata: annotations: - minecluster.dunemask.net/server-name: changeme-server-name + minecluster.dunemask.net/id: changeme-server-id labels: service: changeme-service-name name: changeme-pvc-name diff --git a/lib/k8s/configs/server-svc.yml b/lib/k8s/configs/server-svc.yml index c7e7fa2..f21db9a 100644 --- a/lib/k8s/configs/server-svc.yml +++ b/lib/k8s/configs/server-svc.yml @@ -4,7 +4,7 @@ metadata: annotations: ingress.qumine.io/hostname: changeme-url ingress.qumine.io/portname: minecraft - minecluster.dunemask.net/server-name: changeme-server-name + minecluster.dunemask.net/id: changeme-server-id labels: app: changeme-app name: changeme-name diff --git a/lib/k8s/k8s-server-control.js b/lib/k8s/k8s-server-control.js index 4e5233d..1581ee1 100644 --- a/lib/k8s/k8s-server-control.js +++ b/lib/k8s/k8s-server-control.js @@ -20,10 +20,10 @@ const loadYaml = (f) => yaml.load(fs.readFileSync(path.resolve(f), "utf8")); const mineclusterManaged = (o) => o.metadata && o.metadata.annotations && - o.metadata.annotations["minecluster.dunemask.net/server-name"] !== undefined; + o.metadata.annotations["minecluster.dunemask.net/id"] !== undefined; -export const serverMatch = (serverName) => (o) => - o.metadata.annotations["minecluster.dunemask.net/server-name"] === serverName; +export const serverMatch = (serverId) => (o) => + o.metadata.annotations["minecluster.dunemask.net/id"] === serverId; export async function getDeployments() { const deploymentRes = await k8sDeps.listNamespacedDeployment(namespace); @@ -50,8 +50,9 @@ export async function getVolumes() { return serverVolumes; } -export function getServerAssets(serverName) { - const serverFilter = serverMatch(serverName); +export function getServerAssets(serverId) { + const serverFilter = serverMatch(serverId); + console.log(serverId); return Promise.all([ getDeployments(), getServices(), @@ -69,13 +70,9 @@ export function getServerAssets(serverName) { if (secrets.length > 1) throw Error("Secrets broken!"); const serverAssets = { deployment: deployments[0], - service: services.find( - (s) => s.metadata.name === `mcl-${serverName}-server`, - ), + service: services.find((s) => s.metadata.name.endsWith("-server")), volume: volumes[0], - rconService: services.find( - (s) => s.metadata.name === `mcl-${serverName}-rcon`, - ), + rconService: services.find((s) => s.metadata.name.endsWith("-rcon")), rconSecret: secrets[0], }; for (var k in serverAssets) if (serverAssets[k]) return serverAssets; @@ -84,30 +81,29 @@ export function getServerAssets(serverName) { .catch((e) => ERR("SERVER ASSETS", e)); } -export async function getDeployment(serverName) { +export async function getDeployment(serverId) { const servers = await getDeployments(); + console.log(servers.map(({ metadata }) => metadata.annotations)); const serverDeployment = servers.find( - (s) => - s.metadata.annotations["minecluster.dunemask.net/server-name"] === - serverName, + (s) => s.metadata.annotations["minecluster.dunemask.net/id"] === serverId, ); if (!serverDeployment) - throw Error(`MCL Deployment '${serverName}' could not be found!`); + throw Error(`MCL Deployment with ID '${serverId}' could not be found!`); return serverDeployment; } -export async function getContainers(serverName) { - const deployment = await getDeployment(serverName); +export async function getContainers(serverId) { + const deployment = await getDeployment(serverId); return deployment.spec.template.spec.containers; } -async function containerControl(serverName, deployment, scaleUp) { +async function containerControl(serverId, deployment, scaleUp) { const { containers } = deployment.spec.template.spec; const depFtp = containers.find((c) => c.name.endsWith("-ftp")); const depServer = containers.find((c) => c.name.endsWith("-server")); const depBackup = containers.find((c) => c.name.endsWith("-backup")); - const serverSpec = await getServerEntry(serverName); + const serverSpec = await getServerEntry(serverId); const ftpContainer = depFtp ?? getFtpContainer(serverSpec); const serverContainer = depServer ?? getCoreServerContainer(serverSpec); const backupContainer = depBackup ?? getBackupContainer(serverSpec); @@ -115,10 +111,10 @@ async function containerControl(serverName, deployment, scaleUp) { return [ftpContainer]; } -export async function toggleServer(serverName, scaleUp = false) { - const deployment = await getDeployment(serverName); +export async function toggleServer(serverId, scaleUp = false) { + const deployment = await getDeployment(serverId); deployment.spec.template.spec.containers = await containerControl( - serverName, + serverId, deployment, scaleUp, ); @@ -128,19 +124,3 @@ export async function toggleServer(serverName, scaleUp = false) { deployment, ); } - -export async function scaleDeployment(serverName, scaleUp = false) { - const deployment = await getDeployment(serverName); - if (deployment.spec.replicas === 1 && scaleUp) - return VERB( - "KSC", - `MCL Deployment '${serverName}' is already scaled! Ignoring scale adjustment.`, - ); - deployment.spec.replicas = scaleUp ? 1 : 0; - - return k8sDeps.replaceNamespacedDeployment( - deployment.metadata.name, - namespace, - deployment, - ); -} diff --git a/lib/k8s/server-containers.js b/lib/k8s/server-containers.js index 4694828..a71410f 100644 --- a/lib/k8s/server-containers.js +++ b/lib/k8s/server-containers.js @@ -4,9 +4,9 @@ import yaml from "js-yaml"; const loadYaml = (f) => yaml.load(fs.readFileSync(path.resolve(f), "utf8")); export function getFtpContainer(serverSpec) { - const { name } = serverSpec; + const { mclName } = serverSpec; const ftpContainer = loadYaml("lib/k8s/configs/containers/ftp-server.yml"); - ftpContainer.name = `mcl-${name}-ftp`; + ftpContainer.name = `mcl-${mclName}-ftp`; const ftpPortList = [ { p: 20, n: "ftp-data" }, { p: 21, n: "ftp-commands" }, @@ -22,10 +22,10 @@ export function getFtpContainer(serverSpec) { } export function getCoreServerContainer(serverSpec) { - const { name, version, serverType, memory } = serverSpec; + const { mclName, version, serverType, memory } = serverSpec; const container = loadYaml("lib/k8s/configs/containers/minecraft-server.yml"); // Container Updates - container.name = `mcl-${name}-server`; + container.name = `mcl-${mclName}-server`; container.resources.requests.memory = `${memory}Mi`; const findEnv = (k) => container.env.find(({ name: n }) => n === k); @@ -36,7 +36,7 @@ export function getCoreServerContainer(serverSpec) { updateEnv("VERSION", version); updateEnv("MEMORY", `${memory}M`); // RCON - const rs = `mcl-${name}-rcon-secret`; + const rs = `mcl-${mclName}-rcon-secret`; findEnv("RCON_PASSWORD").valueFrom.secretKeyRef.name = rs; return container; } @@ -50,13 +50,13 @@ export function getServerContainer(serverSpec) { const updateEnv = (k, v) => (findEnv(k).value = v); // Enviornment variables - updateEnv("DIFFICULTY", difficulty); + /*updateEnv("DIFFICULTY", difficulty); updateEnv("MODE", gamemode); updateEnv("MOTD", motd); updateEnv("MAX_PLAYERS", maxPlayers); updateEnv("SEED", seed); updateEnv("OPS", ops); - updateEnv("WHITELIST", whitelist); + updateEnv("WHITELIST", whitelist); */ return container; } diff --git a/lib/k8s/server-control.js b/lib/k8s/server-control.js index bcb5a35..e2b821c 100644 --- a/lib/k8s/server-control.js +++ b/lib/k8s/server-control.js @@ -1,51 +1,42 @@ import k8s from "@kubernetes/client-node"; -import { - getDeployment, - getDeployments, - getServerAssets, - scaleDeployment, -} from "./k8s-server-control.js"; -import { ERR } from "../util/logging.js"; -import ExpressClientError from "../util/ExpressClientError.js"; +import { getDeployments } from "./k8s-server-control.js"; const kc = new k8s.KubeConfig(); kc.loadFromDefault(); const k8sMetrics = new k8s.Metrics(kc); const namespace = process.env.MCL_SERVER_NAMESPACE; -export async function startServerContainer(serverSpec) { - const { name } = serverSpec; - try { - await scaleDeployment(name, true); - } catch (e) { - ERR("SERVER CONTROL", e); - throw new ExpressClientError({ - c: 500, - m: `Error updating server '${name}'!\n`, - }); - } -} +function getServerMetrics(podMetricsRes, serverId, serverAvailable) { + const pod = podMetricsRes.items.find(({ metadata: md }) => { + return ( + md.annotations && + md.annotations["minecluster.dunemask.net/id"] === serverId + ); + }); -export async function stopServerContainer(serverSpec) { - const { name } = serverSpec; - try { - await scaleDeployment(name, false); - } catch (e) { - ERR("SERVER CONTROL", e); - throw new ExpressClientError({ - c: 500, - m: `Error updating server '${name}'!`, - }); + if (serverAvailable && pod) { + const podCpus = pod.containers.map( + ({ usage }) => parseInt(usage.cpu) / 1_000_000, + ); + const podMems = pod.containers.map( + ({ usage }) => parseInt(usage.memory) / 1024, + ); + metrics = { + cpu: Math.ceil(podCpus.reduce((a, b) => a + b)), + memory: Math.ceil(podMems.reduce((a, b) => a + b)), + }; } } export async function getInstances() { const serverDeployments = await getDeployments(); - const podMetricsResponse = await k8sMetrics.getPodMetrics(namespace); - var name, metrics, services, serverAvailable, ftpAvailable; + const podMetricsRes = await k8sMetrics.getPodMetrics(namespace); + var name, serverId, metrics, services, serverAvailable, ftpAvailable; const serverInstances = serverDeployments.map((s) => { - name = s.metadata.annotations["minecluster.dunemask.net/server-name"]; + serverId = s.metadata.annotations["minecluster.dunemask.net/id"]; + name = s.metadata.name; metrics = null; + const { containers } = s.spec.template.spec; services = containers.map(({ name }) => name.split("-").pop()); const serverStatusList = s.status.conditions.map( @@ -57,23 +48,15 @@ export async function getInstances() { ) !== undefined; serverAvailable = services.includes(`server`) && deploymentAvailable; ftpAvailable = services.includes("ftp") && deploymentAvailable; - - const pod = podMetricsResponse.items.find(({ metadata: md }) => { - return md.labels && md.labels.app && md.labels.app === `mcl-${name}-app`; - }); - if (serverAvailable && pod) { - const podCpus = pod.containers.map( - ({ usage }) => parseInt(usage.cpu) / 1_000_000, - ); - const podMems = pod.containers.map( - ({ usage }) => parseInt(usage.memory) / 1024, - ); - metrics = { - cpu: Math.ceil(podCpus.reduce((a, b) => a + b)), - memory: Math.ceil(podMems.reduce((a, b) => a + b)), - }; - } - return { name, metrics, services, serverAvailable, ftpAvailable }; + metrics = getServerMetrics(podMetricsRes, serverId, serverAvailable); + return { + name, + id: serverId, + metrics, + services, + serverAvailable, + ftpAvailable, + }; }); return serverInstances; } diff --git a/lib/k8s/server-create.js b/lib/k8s/server-create.js index 8ef2fe3..4f12993 100644 --- a/lib/k8s/server-create.js +++ b/lib/k8s/server-create.js @@ -4,7 +4,7 @@ import k8s from "@kubernetes/client-node"; import yaml from "js-yaml"; import fs from "node:fs"; import path from "node:path"; -import ExpressClientError from "../util/ExpressClientError.js"; + import { getFtpContainer, getServerContainer, @@ -20,32 +20,31 @@ const namespace = process.env.MCL_SERVER_NAMESPACE; const loadYaml = (f) => yaml.load(fs.readFileSync(path.resolve(f), "utf8")); function createRconSecret(serverSpec) { - const { name } = serverSpec; + const { mclName, id } = serverSpec; const rconYaml = loadYaml("lib/k8s/configs/rcon-secret.yml"); // TODO: Dyamic rconPassword const rconPassword = bcrypt.hashSync(uuidv4(), 10); rconYaml.data["rcon-password"] = Buffer.from(rconPassword).toString("base64"); - rconYaml.metadata.labels.app = `mcl-${name}-app`; - rconYaml.metadata.name = `mcl-${name}-rcon-secret`; + rconYaml.metadata.labels.app = `mcl-${mclName}-app`; + rconYaml.metadata.name = `mcl-${mclName}-rcon-secret`; rconYaml.metadata.namespace = namespace; - rconYaml.metadata.annotations["minecluster.dunemask.net/server-name"] = name; + rconYaml.metadata.annotations["minecluster.dunemask.net/id"] = id; return rconYaml; } function createServerVolume(serverSpec) { - const { name } = serverSpec; + const { mclName, id } = serverSpec; const volumeYaml = loadYaml("lib/k8s/configs/server-pvc.yml"); - volumeYaml.metadata.labels.service = `mcl-${name}-server`; - volumeYaml.metadata.name = `mcl-${name}-volume`; + volumeYaml.metadata.labels.service = `mcl-${mclName}-server`; + volumeYaml.metadata.name = `mcl-${mclName}-volume`; volumeYaml.metadata.namespace = namespace; - volumeYaml.metadata.annotations["minecluster.dunemask.net/server-name"] = - name; + volumeYaml.metadata.annotations["minecluster.dunemask.net/id"] = id; volumeYaml.spec.resources.requests.storage = "1Gi"; // TODO: Changeme return volumeYaml; } function createServerDeploy(serverSpec) { - const { name, host } = serverSpec; + const { mclName, id } = serverSpec; const deployYaml = loadYaml("lib/k8s/configs/server-deployment.yml"); const { metadata } = deployYaml; const serverContainer = getServerContainer(serverSpec); @@ -53,19 +52,21 @@ function createServerDeploy(serverSpec) { const ftpContainer = getFtpContainer(serverSpec); // Configure Metadata; - metadata.name = `mcl-${name}`; + metadata.name = `mcl-${mclName}`; metadata.namespace = namespace; - metadata.annotations["minecluster.dunemask.net/server-name"] = name; + metadata.annotations["minecluster.dunemask.net/id"] = id; deployYaml.metadata = metadata; // Configure Lables & Selectors - deployYaml.spec.selector.matchLabels.app = `mcl-${name}-app`; - deployYaml.spec.template.metadata.labels.app = `mcl-${name}-app`; + deployYaml.spec.selector.matchLabels.app = `mcl-${mclName}-app`; + deployYaml.spec.template.metadata.labels.app = `mcl-${mclName}-app`; + deployYaml.spec.template.metadata.annotations["minecluster.dunemask.net/id"] = + id; // Volumes deployYaml.spec.template.spec.volumes.find( ({ name }) => name === "datadir", - ).persistentVolumeClaim.claimName = `mcl-${name}-volume`; + ).persistentVolumeClaim.claimName = `mcl-${mclName}-volume`; // Apply Containers TODO: User control for autostart deployYaml.spec.template.spec.containers.push(serverContainer); @@ -75,17 +76,16 @@ function createServerDeploy(serverSpec) { } function createServerService(serverSpec) { - const { name, host } = serverSpec; + const { mclName, host, id } = serverSpec; const serviceYaml = loadYaml("lib/k8s/configs/server-svc.yml"); serviceYaml.metadata.annotations["ingress.qumine.io/hostname"] = host; serviceYaml.metadata.annotations["mc-router.itzg.me/externalServerName"] = host; - serviceYaml.metadata.labels.app = `mcl-${name}-app`; - serviceYaml.metadata.name = `mcl-${name}-server`; + serviceYaml.metadata.labels.app = `mcl-${mclName}-app`; + serviceYaml.metadata.name = `mcl-${mclName}-server`; serviceYaml.metadata.namespace = namespace; - serviceYaml.metadata.annotations["minecluster.dunemask.net/server-name"] = - name; - serviceYaml.spec.selector.app = `mcl-${name}-app`; + serviceYaml.metadata.annotations["minecluster.dunemask.net/id"] = id; + serviceYaml.spec.selector.app = `mcl-${mclName}-app`; // Port List: const serverPortList = [{ p: 25565, n: "minecraft" }]; @@ -107,32 +107,26 @@ function createServerService(serverSpec) { return serviceYaml; } -function createRconService(serverSpec) { - const { name } = serverSpec; +function createRconService(createSpec) { + const { id, mclName } = createSpec; const rconSvcYaml = loadYaml("lib/k8s/configs/rcon-svc.yml"); - rconSvcYaml.metadata.labels.app = `mcl-${name}-app`; - rconSvcYaml.metadata.name = `mcl-${name}-rcon`; + rconSvcYaml.metadata.labels.app = `mcl-${mclName}-app`; + rconSvcYaml.metadata.name = `mcl-${mclName}-rcon`; rconSvcYaml.metadata.namespace = namespace; - rconSvcYaml.metadata.annotations["minecluster.dunemask.net/server-name"] = - name; - rconSvcYaml.spec.selector.app = `mcl-${name}-app`; + rconSvcYaml.metadata.annotations["minecluster.dunemask.net/id"] = id; + rconSvcYaml.spec.selector.app = `mcl-${mclName}-app`; return rconSvcYaml; } -export default async function createServerResources(serverSpec) { - const deploymentRes = await k8sDeps.listNamespacedDeployment(namespace); - const deployments = deploymentRes.body.items.map((i) => i.metadata.name); - if (deployments.includes(`mcl-${serverSpec.name}`)) - throw new ExpressClientError({ m: "Server already exists!", c: 409 }); - const pvcRes = await k8sCore.listNamespacedPersistentVolumeClaim(namespace); - const pvcs = pvcRes.body.items.map((i) => i.metadata.name); - if (pvcs.includes(`mcl-${serverSpec.name}-volume`)) - throw new ExpressClientError({ m: "Server PVC already exists!", c: 409 }); - const rconSecret = createRconSecret(serverSpec); - const serverVolume = createServerVolume(serverSpec); - const serverDeploy = createServerDeploy(serverSpec); - const serverService = createServerService(serverSpec); - const rconService = createRconService(serverSpec); +export default async function createServerResources(createSpec) { + createSpec.mclName = `${createSpec.host.replaceAll(".", "-")}-${ + createSpec.id + }`; + const rconSecret = createRconSecret(createSpec); + const serverVolume = createServerVolume(createSpec); + const serverDeploy = createServerDeploy(createSpec); + const serverService = createServerService(createSpec); + const rconService = createRconService(createSpec); k8sCore.createNamespacedPersistentVolumeClaim(namespace, serverVolume); k8sCore.createNamespacedSecret(namespace, rconSecret); k8sCore.createNamespacedService(namespace, serverService); diff --git a/lib/k8s/server-delete.js b/lib/k8s/server-delete.js index e226fbf..75df9f6 100644 --- a/lib/k8s/server-delete.js +++ b/lib/k8s/server-delete.js @@ -22,9 +22,9 @@ function deleteOnExist(o, fn) { } export default async function deleteServerResources(serverSpec) { - const { name } = serverSpec; + const { id } = serverSpec; // Ensure deployment exists - const server = await getServerAssets(name); + const server = await getServerAssets(id); if (!server) throw new ExpressClientError({ c: 404, diff --git a/lib/k8s/server-files.js b/lib/k8s/server-files.js index ec1f7cc..4f04f47 100644 --- a/lib/k8s/server-files.js +++ b/lib/k8s/server-files.js @@ -34,8 +34,8 @@ export async function getFtpClient(serverService) { } export async function useServerFtp(serverSpec, fn) { - const { name } = serverSpec; - const server = await getServerAssets(name); + const { id } = serverSpec; + const server = await getServerAssets(id); if (!server) throw new ExpressClientError({ c: 404, diff --git a/src/components/files/MineclusterFiles.jsx b/src/components/files/MineclusterFiles.jsx index 45bce13..208ec67 100644 --- a/src/components/files/MineclusterFiles.jsx +++ b/src/components/files/MineclusterFiles.jsx @@ -33,14 +33,14 @@ export default function MineclusterFiles(props) { ], [], ); - const { server: serverName } = props; + const { server: serverId } = props; const inputRef = useRef(null); const [dirStack, setDirStack] = useState(["."]); const [files, setFiles] = useState([]); const updateFiles = () => { const dir = dirStack.join("/"); - getServerFiles(serverName, dir).then((f) => { + getServerFiles(serverId, dir).then((f) => { const files = f.map((fi) => ({ ...fi, id: `${dir}/${fi.name}` })); setFiles(files ?? []); }); @@ -70,13 +70,13 @@ export default function MineclusterFiles(props) { function createFolder() { const name = prompt("What is the name of the new folder?"); const path = [...dirStack, name].join("/"); - createServerFolder(serverName, path).then(updateFiles); + createServerFolder(serverId, path).then(updateFiles); } function deleteItems(files) { Promise.all( files.map((f) => - deleteServerItem(serverName, [...dirStack, f.name].join("/"), f.isDir), + deleteServerItem(serverId, [...dirStack, f.name].join("/"), f.isDir), ), ) .catch((e) => console.error("Error deleting some files!", e)) @@ -94,7 +94,7 @@ export default function MineclusterFiles(props) { async function uploadFile(file) { const formData = new FormData(); formData.append("file", file); - formData.append("name", serverName); + formData.append("id", serverId); formData.append("path", [...dirStack, name].join("/")); await fetch("/api/files/upload", { method: "POST", @@ -105,7 +105,7 @@ export default function MineclusterFiles(props) { async function downloadFiles(files) { Promise.all( files.map((f) => - getServerItem(serverName, f.name, [...dirStack, f.name].join("/")), + getServerItem(serverId, f.name, [...dirStack, f.name].join("/")), ), ) .then(() => console.log("Done downloading files!")) diff --git a/src/components/servers/RconDialog.jsx b/src/components/servers/RconDialog.jsx index 5183d1a..c4734b5 100644 --- a/src/components/servers/RconDialog.jsx +++ b/src/components/servers/RconDialog.jsx @@ -16,7 +16,8 @@ export function useRconDialog(isOpen = false) { } export default function RconDialog(props) { - const { serverName, open, dialogToggle } = props; + const { server, open, dialogToggle } = props; + const { name: serverName, id: serverId } = server ?? {}; const theme = useTheme(); const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); return ( @@ -33,7 +34,7 @@ export default function RconDialog(props) { RCON - {serverName} - +