import k8s from "@kubernetes/client-node"; import yaml from "js-yaml"; import { VERB, ERR } from "../util/logging.js"; import { getServerEntry } from "../database/queries/server-queries.js"; import { getFtpContainer, getCoreServerContainer, getBackupContainer, } from "./server-containers.js"; 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; 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; export const serverMatch = (serverName) => (o) => o.metadata.annotations["minecluster.dunemask.net/server-name"] === serverName; export async function getDeployments() { const deploymentRes = await k8sDeps.listNamespacedDeployment(namespace); const serverDeployments = deploymentRes.body.items.filter(mineclusterManaged); return serverDeployments; } export async function getServices() { const serviceRes = await k8sCore.listNamespacedService(namespace); const serverServices = serviceRes.body.items.filter(mineclusterManaged); return serverServices; } export async function getSecrets() { const secretRes = await k8sCore.listNamespacedSecret(namespace); const serverSecrets = secretRes.body.items.filter(mineclusterManaged); return serverSecrets; } export async function getVolumes() { const volumeRes = await k8sCore.listNamespacedPersistentVolumeClaim(namespace); const serverVolumes = volumeRes.body.items.filter(mineclusterManaged); return serverVolumes; } export function getServerAssets(serverName) { const serverFilter = serverMatch(serverName); return Promise.all([ getDeployments(), getServices(), getSecrets(), getVolumes(), ]) .then(([deps, svcs, scrts, vols]) => { const deployments = deps.filter(serverFilter); const services = svcs.filter(serverFilter); const secrets = scrts.filter(serverFilter); const volumes = vols.filter(serverFilter); if (deployments.length > 1) throw Error("Deployment filter broken!"); if (volumes.length > 1) throw Error("Volume filter broken!"); if (secrets.length > 1) throw Error("Secrets broken!"); const serverAssets = { deployment: deployments[0], service: services.find( (s) => s.metadata.name === `mcl-${serverName}-server`, ), volume: volumes[0], rconService: services.find( (s) => s.metadata.name === `mcl-${serverName}-rcon`, ), rconSecret: secrets[0], }; for (var k in serverAssets) if (serverAssets[k]) return serverAssets; // If no assets exist, return nothing }) .catch((e) => ERR("SERVER ASSETS", e)); } export async function getDeployment(serverName) { const servers = await getDeployments(); const serverDeployment = servers.find( (s) => s.metadata.annotations["minecluster.dunemask.net/server-name"] === serverName, ); if (!serverDeployment) throw Error(`MCL Deployment '${serverName}' could not be found!`); return serverDeployment; } export async function getContainers(serverName) { const deployment = await getDeployment(serverName); return deployment.spec.template.spec.containers; } async function containerControl(serverName, 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 ftpContainer = depFtp ?? getFtpContainer(serverSpec); const serverContainer = depServer ?? getCoreServerContainer(serverSpec); const backupContainer = depBackup ?? getBackupContainer(serverSpec); if (scaleUp) return [ftpContainer, serverContainer]; return [ftpContainer]; } export async function toggleServer(serverName, scaleUp = false) { const deployment = await getDeployment(serverName); deployment.spec.template.spec.containers = await containerControl( serverName, deployment, scaleUp, ); return k8sDeps.replaceNamespacedDeployment( deployment.metadata.name, namespace, 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, ); }