minecluster/lib/k8s/k8s-server-control.js

155 lines
5.4 KiB
JavaScript
Raw Normal View History

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";
import { checkAuthorization } from "../database/queries/server-queries.js";
import kc from "./k8s-config.js";
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/id"] !== undefined;
export const serverMatch = (serverId) => (o) =>
o.metadata.annotations["minecluster.dunemask.net/id"] === serverId;
export const cairoMatch = (cairoId) => (o) =>
checkAuthorization(
o.metadata.annotations["minecluster.dunemask.net/id"],
cairoId,
);
export async function getUserDeployments(cairoId) {
const authFIlter = cairoMatch(cairoId);
const allDeployments = await getDeployments();
const authChecks = allDeployments.map(authFIlter);
const authorizations = await Promise.all(authChecks);
return allDeployments.filter((_d, i) => authorizations[i]);
}
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(serverId) {
const serverFilter = serverMatch(serverId);
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 > 2) throw Error("Secrets broken!");
const serverAssets = {
deployment: deployments[0],
service: services.find((s) => s.metadata.name.endsWith("-server")),
volume: volumes[0],
rconService: services.find((s) => s.metadata.name.endsWith("-rcon")),
rconSecret: secrets.find((s) =>
s.metadata.name.endsWith("-rcon-secret"),
),
backupSecret: secrets.find((s) =>
s.metadata.name.endsWith("-backup-secret"),
),
extraService: services.find((s) => s.metadata.name.endsWith("-extra")),
};
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(serverId) {
const servers = await getDeployments();
const serverDeployment = servers.find(
(s) => s.metadata.annotations["minecluster.dunemask.net/id"] === serverId,
);
if (!serverDeployment)
throw Error(`MCL Deployment with ID '${serverId}' could not be found!`);
return serverDeployment;
}
export async function getContainers(serverId) {
const deployment = await getDeployment(serverId);
return deployment.spec.template.spec.containers;
}
async function containerControl(serverSpec, 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 ftpContainer = depFtp ?? getFtpContainer(serverSpec);
const serverContainer = depServer ?? getCoreServerContainer(serverSpec);
const backupContainer = depBackup ?? getBackupContainer(serverSpec);
if (scaleUp && serverSpec.backupEnabled)
return [ftpContainer, serverContainer, backupContainer];
else if (scaleUp) return [ftpContainer, serverContainer];
return [ftpContainer];
}
export function terminationControl(containers) {
return containers.length > 1 ? 30 /*seconds*/ : 1 /*seconds */;
}
export async function toggleServer(serverId, scaleUp = false) {
const [deployment, serverSpec] = await Promise.all([
getDeployment(serverId),
getServerEntry(serverId),
]);
const containers = await containerControl(serverSpec, deployment, scaleUp);
const ts = terminationControl(containers);
// Speed up container termination if not running a server
deployment.spec.template.spec.terminationGracePeriodSeconds = ts;
deployment.spec.template.spec.containers = containers;
return k8sDeps.replaceNamespacedDeployment(
deployment.metadata.name,
namespace,
deployment,
);
}