This commit is contained in:
corenthin 2025-12-12 16:31:50 +01:00
parent 2838b70a27
commit f08de80144
5 changed files with 71 additions and 15 deletions

View File

@ -54,14 +54,20 @@ const vmSchema = zod_1.z.object({
template: zod_1.z.string(),
ip: zod_1.z.string(),
guacamoleIp: zod_1.z.string().optional(),
guacamole_ip: zod_1.z.string().optional(),
bridge: zod_1.z.string().optional(),
model: zod_1.z.enum(['windows', 'linux']).default('linux'),
rdpDomain: zod_1.z.string().optional(),
rdpPort: zod_1.z.number().optional(),
});
}).transform(data => ({
...data,
guacamoleIp: data.guacamoleIp || data.guacamole_ip
}));
const guacamoleUserSchema = zod_1.z.object({
username: zod_1.z.string(),
password: zod_1.z.string(),
ip: zod_1.z.string().optional(),
displayName: zod_1.z.string().optional(),
});
const guacamoleRequestSchema = zod_1.z.object({
groupName: zod_1.z.string().optional(),
@ -81,6 +87,7 @@ const isValidFormationId = (id) => {
};
const apply = async (req, res) => {
try {
logger_1.default.info(`Apply request body: ${JSON.stringify(req.body)}`);
const body = applySchema.parse(req.body);
let formationId = cleanFormationId(body.formationId);
if (!isValidFormationId(formationId)) {

View File

@ -45,6 +45,20 @@ const tfvarsService = __importStar(require("./tfvarsService"));
const shell = __importStar(require("../utils/shell"));
const guacamoleService = __importStar(require("./guacamoleService"));
const logger_1 = __importDefault(require("../utils/logger"));
// Helper to extract numeric suffix for matching (e.g. "USER-1" -> 1)
const extractNumericSuffix = (s) => {
const match = s.match(/(\d+)$/);
return match ? parseInt(match[1], 10) : null;
};
const matchUserForVm = (vmName, users) => {
const vmKey = extractNumericSuffix(vmName);
if (vmKey === null)
return undefined;
return users.find(user => {
const userKey = extractNumericSuffix(user.username);
return userKey === vmKey;
});
};
const prepareRemoteWorkspace = async (formationId) => {
const remoteDir = path_1.default.posix.join(config_1.default.terraform.deployBasePath, formationId);
await ensureRemoteDirectory(remoteDir);
@ -100,12 +114,19 @@ const handleApplyJob = async (formationId, req) => {
let guacamolePromise = Promise.resolve();
if (req.guacamole) {
logger_1.default.info(`Starting Guacamole provisioning for formation ${formationId}`);
guacamolePromise = guacamoleService.provision(formationId, req.guacamole.groupName, req.guacamole.users, req.vms.map(vm => ({
name: vm.name,
ip: vm.guacamoleIp || vm.ip,
rdpDomain: vm.rdpDomain,
rdpPort: vm.rdpPort,
}))).then(() => {
const guacamoleVms = req.vms.map(vm => {
// Try to find matching user to get specific IP (e.g. for static IP assignment via UI)
const user = matchUserForVm(vm.name, req.guacamole.users);
const targetIp = user?.ip || vm.guacamoleIp || vm.ip;
return {
name: vm.name,
ip: targetIp,
rdpDomain: vm.rdpDomain,
rdpPort: vm.rdpPort,
};
});
logger_1.default.info(`Provisioning Guacamole with VMs: ${JSON.stringify(guacamoleVms)}`);
guacamolePromise = guacamoleService.provision(formationId, req.guacamole.groupName, req.guacamole.users, guacamoleVms).then(() => {
logger_1.default.info(`Guacamole provisioning succeeded for formation ${formationId}`);
}).catch((error) => {
logger_1.default.error(`Guacamole provisioning failed for formation ${formationId}: ${error.message}`);

View File

@ -18,15 +18,21 @@ const vmSchema = z.object({
template: z.string(),
ip: z.string(),
guacamoleIp: z.string().optional(),
guacamole_ip: z.string().optional(),
bridge: z.string().optional(),
model: z.enum(['windows', 'linux']).default('linux'),
rdpDomain: z.string().optional(),
rdpPort: z.number().optional(),
});
}).transform(data => ({
...data,
guacamoleIp: data.guacamoleIp || data.guacamole_ip
}));
const guacamoleUserSchema = z.object({
username: z.string(),
password: z.string(),
ip: z.string().optional(),
displayName: z.string().optional(),
});
const guacamoleRequestSchema = z.object({

View File

@ -8,6 +8,22 @@ import * as guacamoleService from './guacamoleService';
import logger from '../utils/logger';
import { ApplyRequest } from '../types/terraform';
// Helper to extract numeric suffix for matching (e.g. "USER-1" -> 1)
const extractNumericSuffix = (s: string): number | null => {
const match = s.match(/(\d+)$/);
return match ? parseInt(match[1], 10) : null;
};
const matchUserForVm = (vmName: string, users: any[]): any | undefined => {
const vmKey = extractNumericSuffix(vmName);
if (vmKey === null) return undefined;
return users.find(user => {
const userKey = extractNumericSuffix(user.username);
return userKey === vmKey;
});
};
const prepareRemoteWorkspace = async (formationId: string): Promise<string> => {
const remoteDir = path.posix.join(config.terraform.deployBasePath, formationId);
@ -75,12 +91,18 @@ export const handleApplyJob = async (formationId: string, req: ApplyRequest) =>
let guacamolePromise: Promise<void> = Promise.resolve();
if (req.guacamole) {
logger.info(`Starting Guacamole provisioning for formation ${formationId}`);
const guacamoleVms = req.vms.map(vm => ({
name: vm.name,
ip: vm.guacamoleIp || vm.ip,
rdpDomain: vm.rdpDomain,
rdpPort: vm.rdpPort,
}));
const guacamoleVms = req.vms.map(vm => {
// Try to find matching user to get specific IP (e.g. for static IP assignment via UI)
const user = matchUserForVm(vm.name, req.guacamole!.users);
const targetIp = user?.ip || vm.guacamoleIp || vm.ip;
return {
name: vm.name,
ip: targetIp,
rdpDomain: vm.rdpDomain,
rdpPort: vm.rdpPort,
};
});
logger.info(`Provisioning Guacamole with VMs: ${JSON.stringify(guacamoleVms)}`);
guacamolePromise = guacamoleService.provision(

View File

@ -15,7 +15,7 @@ export interface VmDefinition {
export interface GuacamoleConfig {
groupName?: string;
users: { username: string; password: string }[];
users: { username: string; password: string; ip?: string; displayName?: string }[];
}
export interface ApplyRequest {