From fc60df27acf36080f82a1fb0b837e5cb138a59ae Mon Sep 17 00:00:00 2001 From: dunemask Date: Tue, 13 Feb 2024 05:09:18 +0000 Subject: [PATCH] [FEATURE] Live Modifications, Host Safety, Minor Tweaks (#19) Co-authored-by: Dunemask Reviewed-on: https://gitea.dunemask.dev/elysium/minecluster/pulls/19 --- dist/app.js | 2 +- lib/controllers/lifecycle-controller.js | 12 ++- lib/database/queries/server-queries.js | 76 ++++++++++++++----- lib/k8s/configs/server-svc.yml | 2 - lib/k8s/server-create.js | 6 +- lib/k8s/server-modify.js | 59 ++++++++++++++ .../server-options/ExtraPortsOption.jsx | 12 ++- src/components/server-options/HostOption.jsx | 10 ++- src/pages/EditCoreOptions.jsx | 6 +- 9 files changed, 154 insertions(+), 31 deletions(-) create mode 100644 lib/k8s/server-modify.js diff --git a/dist/app.js b/dist/app.js index 2f46e88..d87897e 100644 --- a/dist/app.js +++ b/dist/app.js @@ -8,4 +8,4 @@ const kc = new k8s.KubeConfig(); kc.loadFromDefault(); } -main().catch((e)=>{console.log(e)}); +main().catch((e)=>{console.error(e)}); diff --git a/lib/controllers/lifecycle-controller.js b/lib/controllers/lifecycle-controller.js index d5b9e3a..1916157 100644 --- a/lib/controllers/lifecycle-controller.js +++ b/lib/controllers/lifecycle-controller.js @@ -9,6 +9,8 @@ import { import ExpressClientError, { sendError } from "../util/ExpressClientError.js"; import { toggleServer } from "../k8s/k8s-server-control.js"; import { checkAuthorization } from "../database/queries/server-queries.js"; +import { WARN } from "../util/logging.js"; +import modifyServerResources from "../k8s/server-modify.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]))*$`, @@ -69,6 +71,9 @@ function payloadFilter(req, res) { return res .status(400) .send("Extra ports must be a list of strings with length of 5!"); + if (host !== host.toLowerCase()) + WARN("CREATE", "Host automatically being lowercasified..."); + req.body.host = host.toLowerCase(); return "filtered"; } @@ -158,10 +163,15 @@ export async function getServer(req, res) { export async function modifyServer(req, res) { if (payloadFilter(req, res) !== "filtered") return; const serverSpec = req.body; + if (!!serverSpec.host) + WARN( + "MODIFY", + "Warning, hostname changing is not implimented yet! Please ask the developer if you'd like to see this added!", + ); try { await checkServerId(req.cairoId, serverSpec); const serverEntry = await modifyServerEntry(serverSpec); - // await createServerResources(serverEntry); + await modifyServerResources(serverEntry); res.sendStatus(200); } catch (e) { sendError(res)(e); diff --git a/lib/database/queries/server-queries.js b/lib/database/queries/server-queries.js index 326572b..a08d05b 100644 --- a/lib/database/queries/server-queries.js +++ b/lib/database/queries/server-queries.js @@ -165,7 +165,7 @@ export async function modifyServerEntry(serverSpec) { id, // ownerCairoId: owner_cairo_id, // DIsabled! If these becomes a reqest, please create a new function! name, - host, + // host, // TODO: Can only be updated if service name is generic and non descriptive version, serverType: server_type, cpu, // TODO: Ignored for now by the K8S manifests @@ -180,28 +180,66 @@ export async function modifyServerEntry(serverSpec) { backupInterval: backup_interval, } = serverSpec; - const q = updateWhereAllQuery( - table, - { + const q = + updateWhereAllQuery( + table, + { + name, + // host, // TODO: Can only be updated if service name is generic and non descriptive + version, + server_type, + cpu, // TODO: Ignored for now by the K8S manifests + memory, + // storage, // DO NOT INCLUDE THIS KEY, Not all storage providers in kubernetes allow for dynamically resizable PVCs + extra_ports, + backup_enabled, + backup_host, + backup_bucket_path, + backup_id, + backup_key, + backup_interval, + }, + { id }, + ) + ` RETURNING *;`; + try { + const entries = await pg.query(q); + const { name, - host, + host, // Should always read the database value + server_type: serverType, + storage, + extra_ports: extraPorts, + backup_enabled: backupEnabled, + backup_host: backupHost, + backup_bucket_path: backupPath, + backup_id: backupId, + backup_key: backupKey, + backup_interval: backupInterval, + } = entries[0]; + + const mclName = getMclName(host, id); + + return { + name, // Could change + mclName, // Shouldn't change + id, // Won't change + // host, // TODO: Can only be updated if service name is generic and non descriptive version, - server_type, + serverType, cpu, // TODO: Ignored for now by the K8S manifests memory, - // storage, // DO NOT INCLUDE THIS KEY, Not all storage providers in kubernetes allow for dynamically resizable PVCs - extra_ports, - backup_enabled, - backup_host, - backup_bucket_path, - backup_id, - backup_key, - backup_interval, - }, - { id }, - ); - - return pg.query(q); + storage, + extraPorts, + backupEnabled, + backupHost, + backupPath, + backupId, + backupKey, + backupInterval, + }; + } catch (e) { + asExpressClientError(e); + } } export async function getServerEntries() { diff --git a/lib/k8s/configs/server-svc.yml b/lib/k8s/configs/server-svc.yml index f21db9a..a6f520e 100644 --- a/lib/k8s/configs/server-svc.yml +++ b/lib/k8s/configs/server-svc.yml @@ -11,8 +11,6 @@ metadata: namespace: changeme-namespace spec: internalTrafficPolicy: Cluster - ipFamilies: - - IPv4 ipFamilyPolicy: SingleStack ports: # Programatically add all FTP ports. Port range includes 20, 21, 40000-40001 - name: minecraft diff --git a/lib/k8s/server-create.js b/lib/k8s/server-create.js index 30e21fa..7d09e61 100644 --- a/lib/k8s/server-create.js +++ b/lib/k8s/server-create.js @@ -18,7 +18,7 @@ const namespace = process.env.MCL_SERVER_NAMESPACE; const loadYaml = (f) => yaml.load(fs.readFileSync(path.resolve(f), "utf8")); -function createExtraService(serverSpec) { +export function createExtraService(serverSpec) { const { mclName, id, extraPorts } = serverSpec; if (!extraPorts) return; const serviceYaml = loadYaml("lib/k8s/configs/extra-svc.yml"); @@ -49,7 +49,7 @@ function createExtraService(serverSpec) { return serviceYaml; } -function createBackupSecret(serverSpec) { +export function createBackupSecret(serverSpec) { if (!serverSpec.backupEnabled) return; // If backup not defined, don't create RCLONE secret const { mclName, id, backupId, backupKey, backupHost } = serverSpec; const backupYaml = loadYaml("lib/k8s/configs/backup-secret.yml"); @@ -153,7 +153,7 @@ function createServerDeploy(serverSpec) { return deployYaml; } -function createServerService(serverSpec) { +export function createServerService(serverSpec) { const { mclName, host, id } = serverSpec; const serviceYaml = loadYaml("lib/k8s/configs/server-svc.yml"); serviceYaml.metadata.annotations["ingress.qumine.io/hostname"] = host; diff --git a/lib/k8s/server-modify.js b/lib/k8s/server-modify.js new file mode 100644 index 0000000..74a1428 --- /dev/null +++ b/lib/k8s/server-modify.js @@ -0,0 +1,59 @@ +import k8s from "@kubernetes/client-node"; +import { + createExtraService, + createBackupSecret, + createServerService, +} from "./server-create.js"; +import kc from "./k8s-config.js"; +import { getServerAssets } from "./k8s-server-control.js"; +const k8sCore = kc.makeApiClient(k8s.CoreV1Api); +const namespace = process.env.MCL_SERVER_NAMESPACE; + +export default async function modifyServerResources(modifySpec) { + const { id: serverId } = modifySpec; + const serverAssets = await getServerAssets(serverId); + const serverService = createServerService(modifySpec); + const extraService = createExtraService(modifySpec); + const backupSecret = createBackupSecret(modifySpec); + const serverResources = []; + + if (!!serverService) + // Will Always Exist + serverResources.push( + k8sCore.replaceNamespacedService( + serverAssets.service.metadata.name, + namespace, + serverService, + ), + ); + + if (!!extraService && !!serverAssets.extraService) + // Might not exist + serverResources.push( + k8sCore.replaceNamespacedService( + serverAssets.extraService.metadata.name, + namespace, + extraService, + ), + ); + else if (!!extraService) + serverResources.push( + k8sCore.createNamespacedService(namespace, extraService), + ); + + if (!!backupSecret && !!serverAssets.backupSecret) + // Might not exist + serverResources.push( + k8sCore.replaceNamespacedSecret( + serverAssets.backupSecret.metadata.name, + namespace, + backupSecret, + ), + ); + else if (!!backupSecret) + serverResources.push( + k8sCore.createNamespacedSecret(namespace, backupSecret), + ); + + return await Promise.all(serverResources); +} diff --git a/src/components/server-options/ExtraPortsOption.jsx b/src/components/server-options/ExtraPortsOption.jsx index c97b40b..893a067 100644 --- a/src/components/server-options/ExtraPortsOption.jsx +++ b/src/components/server-options/ExtraPortsOption.jsx @@ -3,7 +3,8 @@ import TextField from "@mui/material/TextField"; import Autocomplete from "@mui/material/Autocomplete"; import Chip from "@mui/material/Chip"; -const validatePort = (p) => p !== "25565" && p !== "25575" && p.length < 6; +const validatePort = (p) => + p !== "25565" && p !== "25575" && p.length < 6 && parseInt(p) < 60_000; export default function ExtraPortsOption(props) { const { extraPorts: initExtraPorts } = props; @@ -30,7 +31,14 @@ export default function ExtraPortsOption(props) { value={extraPorts} onChange={portChange} freeSolo - renderInput={(p) => } + renderInput={(p) => ( + + )} renderTags={(value, getTagProps) => value.map((option, index) => { const defaultChipProps = getTagProps({ index }); diff --git a/src/components/server-options/HostOption.jsx b/src/components/server-options/HostOption.jsx index d03d1db..15d14e9 100644 --- a/src/components/server-options/HostOption.jsx +++ b/src/components/server-options/HostOption.jsx @@ -1,15 +1,21 @@ import TextField from "@mui/material/TextField"; export default function HostOption(props) { - const { value, onChange } = props; + const { value, onChange, disabled } = props; + + function onTextChange(e) { + e.target.value = e.target.value.toLowerCase(); + onChange(e); + } return ( ); } diff --git a/src/pages/EditCoreOptions.jsx b/src/pages/EditCoreOptions.jsx index a098cb6..bda2c34 100644 --- a/src/pages/EditCoreOptions.jsx +++ b/src/pages/EditCoreOptions.jsx @@ -73,7 +73,11 @@ export default function EditCoreOptions(props) { > - +