mirror of
https://github.com/YuzuZensai/Minikura.git
synced 2026-09-14 03:09:50 +00:00
✨ feat: topology, and improves handling
This commit is contained in:
@@ -1,57 +1,53 @@
|
||||
import type { PrismaClient } from "@minikura/db";
|
||||
import { KubernetesClient } from "../utils/k8s-client";
|
||||
import type { Logger } from "pino";
|
||||
import { SYNC_INTERVAL } from "../config/constants";
|
||||
import { KubernetesClient } from "../utils/k8s-client";
|
||||
import { createLogger } from "../utils/logger";
|
||||
|
||||
export abstract class BaseController {
|
||||
protected prisma: PrismaClient;
|
||||
protected k8sClient: KubernetesClient;
|
||||
protected namespace: string;
|
||||
protected logger: Logger;
|
||||
private intervalId: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
constructor(prisma: PrismaClient, namespace: string) {
|
||||
this.prisma = prisma;
|
||||
this.k8sClient = KubernetesClient.getInstance();
|
||||
this.namespace = namespace;
|
||||
this.logger = createLogger({ controller: this.getControllerName() });
|
||||
}
|
||||
|
||||
/**
|
||||
* Start watching for changes in the database and syncing to Kubernetes
|
||||
*/
|
||||
public startWatching(): void {
|
||||
console.log(`Starting to watch for changes in ${this.getControllerName()}...`);
|
||||
this.logger.info(
|
||||
{ namespace: this.namespace, syncInterval: SYNC_INTERVAL },
|
||||
"Starting controller watch loop"
|
||||
);
|
||||
|
||||
// Initial sync
|
||||
this.syncResources().catch((err) => {
|
||||
console.error(`Error during initial sync of ${this.getControllerName()}:`, err);
|
||||
this.logger.error({ err }, "Error during initial resource synchronization");
|
||||
});
|
||||
|
||||
// Polling interval for changes
|
||||
// TODO: Maybe there's a better way to do this
|
||||
this.intervalId = setInterval(() => {
|
||||
this.syncResources().catch((err) => {
|
||||
console.error(`Error syncing ${this.getControllerName()}:`, err);
|
||||
this.logger.error({ err }, "Error during periodic resource synchronization");
|
||||
});
|
||||
}, SYNC_INTERVAL);
|
||||
|
||||
this.logger.debug(
|
||||
{ intervalMs: SYNC_INTERVAL },
|
||||
"Polling interval established for resource synchronization"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop watching for changes
|
||||
*/
|
||||
public stopWatching(): void {
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId);
|
||||
this.intervalId = null;
|
||||
console.log(`Stopped watching for changes in ${this.getControllerName()}`);
|
||||
this.logger.info("Controller watch loop stopped");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a name for this controller for logging purposes
|
||||
*/
|
||||
protected abstract getControllerName(): string;
|
||||
|
||||
/**
|
||||
* Sync resources from database to Kubernetes
|
||||
*/
|
||||
protected abstract syncResources(): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import type { PrismaClient } from "@minikura/db";
|
||||
import type { ReverseProxyServer, CustomEnvironmentVariable } from "@minikura/db";
|
||||
import { BaseController } from "./base-controller";
|
||||
import type { ReverseProxyConfig } from "../types";
|
||||
import type { CustomEnvironmentVariable, ReverseProxyServer } from "@minikura/db";
|
||||
import {
|
||||
createReverseProxyServer,
|
||||
deleteReverseProxyServer,
|
||||
} from "../resources/reverseProxyServer";
|
||||
import type { ReverseProxyConfig } from "../types";
|
||||
import { BaseController } from "./base-controller";
|
||||
|
||||
type ReverseProxyWithEnvVars = ReverseProxyServer & {
|
||||
env_variables: CustomEnvironmentVariable[];
|
||||
@@ -14,10 +13,6 @@ type ReverseProxyWithEnvVars = ReverseProxyServer & {
|
||||
export class ReverseProxyController extends BaseController {
|
||||
private deployedProxies = new Map<string, ReverseProxyWithEnvVars>();
|
||||
|
||||
constructor(prisma: PrismaClient, namespace: string) {
|
||||
super(prisma, namespace);
|
||||
}
|
||||
|
||||
protected getControllerName(): string {
|
||||
return "ReverseProxyController";
|
||||
}
|
||||
@@ -36,25 +31,32 @@ export class ReverseProxyController extends BaseController {
|
||||
|
||||
const currentProxyIds = new Set(proxies.map((proxy) => proxy.id));
|
||||
|
||||
// Delete reverse proxy servers that are no longer in the database
|
||||
for (const [proxyId, proxy] of this.deployedProxies.entries()) {
|
||||
if (!currentProxyIds.has(proxyId)) {
|
||||
console.log(
|
||||
`Reverse proxy server ${proxy.id} (${proxyId}) has been removed from the database, deleting from Kubernetes...`
|
||||
this.logger.info(
|
||||
{ proxyId, proxyType: proxy.type },
|
||||
"Reverse proxy removed from database, deleting K8s resources"
|
||||
);
|
||||
await deleteReverseProxyServer(proxy.id, proxy.type, appsApi, coreApi, this.namespace);
|
||||
this.deployedProxies.delete(proxyId);
|
||||
}
|
||||
}
|
||||
|
||||
// Create or update reverse proxy servers that are in the database
|
||||
for (const proxy of proxies) {
|
||||
const deployedProxy = this.deployedProxies.get(proxy.id);
|
||||
|
||||
// If proxy doesn't exist yet or has been updated
|
||||
if (!deployedProxy || this.hasProxyChanged(deployedProxy, proxy)) {
|
||||
console.log(
|
||||
`${!deployedProxy ? "Creating" : "Updating"} reverse proxy server ${proxy.id} (${proxy.id}) in Kubernetes...`
|
||||
const action = !deployedProxy ? "Creating" : "Updating";
|
||||
this.logger.info(
|
||||
{
|
||||
proxyId: proxy.id,
|
||||
proxyType: proxy.type,
|
||||
action: action.toLowerCase(),
|
||||
externalAddress: proxy.external_address,
|
||||
externalPort: proxy.external_port,
|
||||
listenPort: proxy.listen_port,
|
||||
},
|
||||
`${action} reverse proxy server in Kubernetes`
|
||||
);
|
||||
|
||||
const proxyConfig: ReverseProxyConfig = {
|
||||
@@ -66,6 +68,7 @@ export class ReverseProxyController extends BaseController {
|
||||
apiKey: proxy.api_key,
|
||||
type: proxy.type,
|
||||
memory: proxy.memory,
|
||||
service_type: proxy.service_type,
|
||||
env_variables: proxy.env_variables?.map((ev) => ({
|
||||
key: ev.key,
|
||||
value: ev.value,
|
||||
@@ -80,12 +83,11 @@ export class ReverseProxyController extends BaseController {
|
||||
this.namespace
|
||||
);
|
||||
|
||||
// Update cache
|
||||
this.deployedProxies.set(proxy.id, { ...proxy });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error syncing reverse proxy servers:", error);
|
||||
this.logger.error({ err: error }, "Failed to sync reverse proxy servers to Kubernetes");
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -94,20 +96,20 @@ export class ReverseProxyController extends BaseController {
|
||||
oldProxy: ReverseProxyWithEnvVars,
|
||||
newProxy: ReverseProxyWithEnvVars
|
||||
): boolean {
|
||||
// Check basic properties
|
||||
const basicPropsChanged =
|
||||
oldProxy.external_address !== newProxy.external_address ||
|
||||
oldProxy.external_port !== newProxy.external_port ||
|
||||
oldProxy.listen_port !== newProxy.listen_port ||
|
||||
oldProxy.description !== newProxy.description;
|
||||
oldProxy.description !== newProxy.description ||
|
||||
oldProxy.service_type !== newProxy.service_type;
|
||||
|
||||
if (basicPropsChanged) return true;
|
||||
|
||||
// Check if environment variables have changed
|
||||
const oldEnvVars = oldProxy.env_variables || [];
|
||||
const newEnvVars = newProxy.env_variables || [];
|
||||
|
||||
if (oldEnvVars.length !== newEnvVars.length) return true;
|
||||
|
||||
for (const newEnv of newEnvVars) {
|
||||
const oldEnv = oldEnvVars.find((e) => e.key === newEnv.key);
|
||||
if (!oldEnv || oldEnv.value !== newEnv.value) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { type PrismaClient, ServerType } from "@minikura/db";
|
||||
import type { Server, CustomEnvironmentVariable } from "@minikura/db";
|
||||
import { BaseController } from "./base-controller";
|
||||
import type { ServerConfig } from "../types";
|
||||
import type { CustomEnvironmentVariable, Server } from "@minikura/db";
|
||||
import { createServer, deleteServer } from "../resources/server";
|
||||
import type { ServerConfig } from "../types";
|
||||
import { BaseController } from "./base-controller";
|
||||
|
||||
type ServerWithEnvVars = Server & {
|
||||
env_variables: CustomEnvironmentVariable[];
|
||||
@@ -11,10 +10,6 @@ type ServerWithEnvVars = Server & {
|
||||
export class ServerController extends BaseController {
|
||||
private deployedServers = new Map<string, ServerWithEnvVars>();
|
||||
|
||||
constructor(prisma: PrismaClient, namespace: string) {
|
||||
super(prisma, namespace);
|
||||
}
|
||||
|
||||
protected getControllerName(): string {
|
||||
return "ServerController";
|
||||
}
|
||||
@@ -33,25 +28,31 @@ export class ServerController extends BaseController {
|
||||
|
||||
const currentServerIds = new Set(servers.map((server) => server.id));
|
||||
|
||||
// Delete servers that are no longer in the database
|
||||
for (const [serverId, server] of this.deployedServers.entries()) {
|
||||
if (!currentServerIds.has(serverId)) {
|
||||
console.log(
|
||||
`Server ${server.id} (${serverId}) has been removed from the database, deleting from Kubernetes...`
|
||||
this.logger.info(
|
||||
{ serverId, serverName: server.id },
|
||||
"Server removed from database, deleting K8s resources"
|
||||
);
|
||||
await deleteServer(serverId, appsApi, coreApi, this.namespace);
|
||||
this.deployedServers.delete(serverId);
|
||||
}
|
||||
}
|
||||
|
||||
// Create or update servers that are in the database
|
||||
for (const server of servers) {
|
||||
const deployedServer = this.deployedServers.get(server.id);
|
||||
|
||||
// If server doesn't exist yet or has been updated
|
||||
if (!deployedServer || this.hasServerChanged(deployedServer, server)) {
|
||||
console.log(
|
||||
`${!deployedServer ? "Creating" : "Updating"} server ${server.id} (${server.id}) in Kubernetes...`
|
||||
const action = !deployedServer ? "Creating" : "Updating";
|
||||
this.logger.info(
|
||||
{
|
||||
serverId: server.id,
|
||||
serverType: server.type,
|
||||
action: action.toLowerCase(),
|
||||
memory: server.memory,
|
||||
port: server.listen_port,
|
||||
},
|
||||
`${action} Minecraft server in Kubernetes`
|
||||
);
|
||||
|
||||
const serverConfig: ServerConfig = {
|
||||
@@ -61,6 +62,7 @@ export class ServerController extends BaseController {
|
||||
description: server.description,
|
||||
listen_port: server.listen_port,
|
||||
memory: server.memory,
|
||||
service_type: server.service_type,
|
||||
env_variables: server.env_variables?.map((ev) => ({
|
||||
key: ev.key,
|
||||
value: ev.value,
|
||||
@@ -69,33 +71,29 @@ export class ServerController extends BaseController {
|
||||
|
||||
await createServer(serverConfig, appsApi, coreApi, networkingApi, this.namespace);
|
||||
|
||||
// Update cache
|
||||
this.deployedServers.set(server.id, { ...server });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error syncing servers:", error);
|
||||
this.logger.error({ err: error }, "Failed to sync servers to Kubernetes");
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private hasServerChanged(oldServer: ServerWithEnvVars, newServer: ServerWithEnvVars): boolean {
|
||||
// Check basic properties
|
||||
const basicPropsChanged =
|
||||
oldServer.type !== newServer.type ||
|
||||
oldServer.listen_port !== newServer.listen_port ||
|
||||
oldServer.description !== newServer.description;
|
||||
oldServer.description !== newServer.description ||
|
||||
oldServer.service_type !== newServer.service_type;
|
||||
|
||||
if (basicPropsChanged) return true;
|
||||
|
||||
// Check if environment variables have changed
|
||||
const oldEnvVars = oldServer.env_variables || [];
|
||||
const newEnvVars = newServer.env_variables || [];
|
||||
|
||||
// Check if the number of env vars has changed
|
||||
if (oldEnvVars.length !== newEnvVars.length) return true;
|
||||
|
||||
// Check if any of the existing env vars have changed
|
||||
for (const newEnv of newEnvVars) {
|
||||
const oldEnv = oldEnvVars.find((e) => e.key === newEnv.key);
|
||||
if (!oldEnv || oldEnv.value !== newEnv.value) {
|
||||
|
||||
Reference in New Issue
Block a user