(feature) Update UI & Resource Availability
This commit is contained in:
parent
11d8229eb5
commit
929193d272
44 changed files with 4747 additions and 27 deletions
171
lib/k8s/server-create.js
Normal file
171
lib/k8s/server-create.js
Normal file
|
@ -0,0 +1,171 @@
|
|||
import { v4 as uuidv4 } from "uuid";
|
||||
import bcrypt from "bcrypt";
|
||||
import k8s from "@kubernetes/client-node";
|
||||
import yaml from "js-yaml";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
const kc = new k8s.KubeConfig();
|
||||
kc.loadFromDefault();
|
||||
const k8sDeps = kc.makeApiClient(k8s.AppsV1Api);
|
||||
const k8sCore = kc.makeApiClient(k8s.CoreV1Api);
|
||||
const namespace = process.env.MCL_SERVER_NAMESPACE;
|
||||
|
||||
function payloadFilter(req, res) {
|
||||
const serverSpec = req.body;
|
||||
if (!serverSpec) return res.sendStatus(400);
|
||||
const { name, url, version, serverType, difficulty, gamemode, memory } =
|
||||
serverSpec;
|
||||
if (!name) return res.status(400).send("Server name is required!");
|
||||
if (!url) return res.status(400).send("Server url is required!");
|
||||
if (!version) return res.status(400).send("Server version is required!");
|
||||
if (!difficulty)
|
||||
return res.status(400).send("Server difficulty is required!");
|
||||
if (!serverType) return res.status(400).send("Server type is required!");
|
||||
if (!gamemode) return res.status(400).send("Server Gamemode is required!");
|
||||
if (!memory) return res.status(400).send("Memory is required!");
|
||||
req.body.name = req.body.name.toLowerCase();
|
||||
return "filtered";
|
||||
}
|
||||
|
||||
function createRconSecret(serverSpec) {
|
||||
const { name } = serverSpec;
|
||||
const rconYaml = yaml.load(
|
||||
fs.readFileSync(path.resolve("lib/k8s/configs/rcon-secret.yml"), "utf8")
|
||||
);
|
||||
|
||||
// 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.namespace = namespace;
|
||||
return rconYaml;
|
||||
}
|
||||
|
||||
function createServerVolume(serverSpec) {
|
||||
const { name } = serverSpec;
|
||||
const volumeYaml = yaml.load(
|
||||
fs.readFileSync(path.resolve("lib/k8s/configs/server-pvc.yml"), "utf8")
|
||||
);
|
||||
volumeYaml.metadata.labels.service = `mcl-${name}-server`;
|
||||
volumeYaml.metadata.name = `mcl-${name}-volume`;
|
||||
volumeYaml.metadata.namespace = namespace;
|
||||
volumeYaml.spec.resources.requests.storage = "1Gi"; // TODO: Changeme
|
||||
return volumeYaml;
|
||||
}
|
||||
|
||||
function createServerDeploy(serverSpec) {
|
||||
const {
|
||||
name,
|
||||
version,
|
||||
serverType,
|
||||
difficulty,
|
||||
gamemode,
|
||||
memory,
|
||||
motd,
|
||||
maxPlayers,
|
||||
seed,
|
||||
modpack,
|
||||
ops,
|
||||
whitelist,
|
||||
} = serverSpec;
|
||||
const deployYaml = yaml.load(
|
||||
fs.readFileSync(
|
||||
path.resolve("lib/k8s/configs/server-deployment.yml"),
|
||||
"utf8"
|
||||
)
|
||||
);
|
||||
deployYaml.metadata.name = `mcl-${name}`;
|
||||
deployYaml.metadata.namespace = namespace;
|
||||
deployYaml.spec.replicas = 0; // TODO: User control for autostart
|
||||
deployYaml.spec.selector.matchLabels.app = `mcl-${name}-app`;
|
||||
deployYaml.spec.template.metadata.labels.app = `mcl-${name}-app`;
|
||||
deployYaml.spec.template.spec.containers.splice(0, 1); //TODO: Currently removing backup container
|
||||
const serverContainer = deployYaml.spec.template.spec.containers[0];
|
||||
|
||||
// Enviornment variables
|
||||
serverContainer.env.find(({ name: n }) => n === "TYPE").value = serverType;
|
||||
serverContainer.env.find(({ name: n }) => n === "VERSION").value = version;
|
||||
serverContainer.env.find(({ name: n }) => n === "DIFFICULTY").value =
|
||||
difficulty;
|
||||
serverContainer.env.find(({ name: n }) => n === "MODE").value = gamemode;
|
||||
serverContainer.env.find(({ name: n }) => n === "MOTD").value = motd;
|
||||
serverContainer.env.find(({ name: n }) => n === "MAX_PLAYERS").value =
|
||||
maxPlayers;
|
||||
serverContainer.env.find(({ name: n }) => n === "SEED").value = seed;
|
||||
serverContainer.env.find(({ name: n }) => n === "OPS").value = ops;
|
||||
serverContainer.env.find(({ name: n }) => n === "WHITELIST").value =
|
||||
whitelist;
|
||||
serverContainer.env.find(
|
||||
({ name: n }) => n === "MEMORY"
|
||||
).value = `${memory}M`;
|
||||
if (version !== "VANILLA")
|
||||
delete serverContainer.env.find(({ name: n }) => n === "MODPACK").value;
|
||||
else
|
||||
serverContainer.env.find(({ name: n }) => n === "MODPACK").value = modpack;
|
||||
|
||||
serverContainer.env.find(
|
||||
({ name }) => name === "RCON_PASSWORD"
|
||||
).valueFrom.secretKeyRef.name = `mcl-${name}-rcon-secret`;
|
||||
// Server Container Name
|
||||
serverContainer.name = `mcl-${name}`;
|
||||
// Resources
|
||||
serverContainer.resources.requests.memory = `${memory}Mi`;
|
||||
// serverContainer.resources.limits.memory = `${memory}Mi`; // TODO Allow for limits beyond initial startup
|
||||
// Volumes
|
||||
deployYaml.spec.template.spec.volumes.find(
|
||||
({ name }) => name === "datadir"
|
||||
).persistentVolumeClaim.claimName = `mcl-${name}-volume`;
|
||||
deployYaml.spec.template.spec.containers[0] = serverContainer;
|
||||
return deployYaml;
|
||||
}
|
||||
|
||||
function createServerService(serverSpec) {
|
||||
const { name, url } = serverSpec;
|
||||
const serviceYaml = yaml.load(
|
||||
fs.readFileSync(path.resolve("lib/k8s/configs/server-svc.yml"), "utf8")
|
||||
);
|
||||
serviceYaml.metadata.annotations["ingress.qumine.io/hostname"] = url;
|
||||
serviceYaml.metadata.labels.app = `mcl-${name}-app`;
|
||||
serviceYaml.metadata.name = `mcl-${name}-server`;
|
||||
serviceYaml.metadata.namespace = namespace;
|
||||
serviceYaml.spec.selector.app = `mcl-${name}-app`;
|
||||
return serviceYaml;
|
||||
}
|
||||
|
||||
function createRconService(serverSpec) {
|
||||
const { name, url } = serverSpec;
|
||||
const rconSvcYaml = yaml.load(
|
||||
fs.readFileSync(path.resolve("lib/k8s/configs/rcon-svc.yml"), "utf8")
|
||||
);
|
||||
rconSvcYaml.metadata.labels.app = `mcl-${name}-app`;
|
||||
rconSvcYaml.metadata.name = `mcl-${name}-rcon`;
|
||||
rconSvcYaml.metadata.namespace = namespace;
|
||||
rconSvcYaml.spec.selector.app = `mcl-${name}-app`;
|
||||
return rconSvcYaml;
|
||||
}
|
||||
|
||||
export default async function createServer(req, res) {
|
||||
if (payloadFilter(req, res) !== "filtered") return;
|
||||
const serverSpec = req.body;
|
||||
const deploymentRes = await k8sDeps.listNamespacedDeployment(namespace);
|
||||
const deployments = deploymentRes.body.items.map((i) => i.metadata.name);
|
||||
if (deployments.includes(`mcl-${serverSpec.name}`))
|
||||
return res.status(409).send("Server already exists!");
|
||||
const pvcRes = await k8sCore.listNamespacedPersistentVolumeClaim(namespace);
|
||||
const pvcs = pvcRes.body.items.map((i) => i.metadata.name);
|
||||
if (pvcs.includes(`mcl-${serverSpec.name}-volume`))
|
||||
return res.status(409).send("Server PVC already exists!");
|
||||
const rconSecret = createRconSecret(serverSpec);
|
||||
const serverVolume = createServerVolume(serverSpec);
|
||||
const serverDeploy = createServerDeploy(serverSpec);
|
||||
const serverService = createServerService(serverSpec);
|
||||
const rconService = createRconService(serverSpec);
|
||||
k8sCore.createNamespacedPersistentVolumeClaim(namespace, serverVolume);
|
||||
k8sCore.createNamespacedSecret(namespace, rconSecret);
|
||||
k8sCore.createNamespacedService(namespace, serverService);
|
||||
k8sCore.createNamespacedService(namespace, rconService);
|
||||
k8sDeps.createNamespacedDeployment(namespace, serverDeploy);
|
||||
|
||||
res.sendStatus(200);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue