From 17db53403a827175c436cd81c474080f60c31cab Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Sun, 11 May 2025 00:01:38 +0100 Subject: [PATCH] Move filepath into config class --- packages/controller/controller.ts | 17 +++++------------ packages/controller/src/Controller.ts | 5 +---- packages/ctl/ctl.ts | 11 ++++++----- packages/host/host.ts | 12 ++++++------ packages/host/src/Host.ts | 15 +++++---------- packages/lib/src/config/classes.ts | 14 ++++++++++---- packages/lib/src/config/definitions.ts | 10 ++++++---- packages/lib/src/shared_commands.ts | 3 +-- test/controller/Controller.js | 2 +- test/controller/WsServer.js | 2 +- test/host.js | 13 +++++++------ test/integration/routing.js | 9 ++------- 12 files changed, 51 insertions(+), 62 deletions(-) diff --git a/packages/controller/controller.ts b/packages/controller/controller.ts index 45e7c168..5ec98b0c 100644 --- a/packages/controller/controller.ts +++ b/packages/controller/controller.ts @@ -210,7 +210,6 @@ interface InitializeParameters { shouldRun: boolean; clusterLogger: winston.Logger; pluginInfos: lib.PluginNodeEnvInfo[] | null; - controllerConfigPath: string | null; controllerConfig: lib.ControllerConfig | null; } @@ -334,7 +333,6 @@ async function initialize(): Promise { shouldRun, clusterLogger, pluginInfos: null, - controllerConfigPath: null, controllerConfig: null, }; } @@ -344,17 +342,17 @@ async function initialize(): Promise { lib.registerPluginMessages(pluginInfos); lib.addPluginConfigFields(pluginInfos); + let controllerConfig; const controllerConfigPath = args.config; logger.info(`Loading config from ${controllerConfigPath}`); - let controllerConfig = new lib.ControllerConfig("controller"); try { let jsonConfig = JSON.parse(await fs.readFile(controllerConfigPath, { encoding: "utf8" })); - controllerConfig = lib.ControllerConfig.fromJSON(jsonConfig, "controller"); + controllerConfig = lib.ControllerConfig.fromJSON(jsonConfig, "controller", controllerConfigPath); } catch (err: any) { if (err.code === "ENOENT") { logger.info("Config not found, initializing new config"); - controllerConfig = new lib.ControllerConfig("controller"); + controllerConfig = new lib.ControllerConfig("controller", undefined, controllerConfigPath); } else { throw new lib.StartupError(`Failed to load ${controllerConfigPath}: ${err.message}`); @@ -374,9 +372,7 @@ async function initialize(): Promise { } if (command === "config") { - await lib.handleConfigCommand( - args, controllerConfig, controllerConfigPath - ); + await lib.handleConfigCommand(args, controllerConfig); } else if (command === "bootstrap") { await handleBootstrapCommand(args, controllerConfig); @@ -386,7 +382,6 @@ async function initialize(): Promise { args, clusterLogger, pluginInfos, - controllerConfigPath, controllerConfig, shouldRun, }; @@ -402,17 +397,15 @@ async function startup() { shouldRun, clusterLogger, pluginInfos, - controllerConfigPath, controllerConfig, } = await initialize(); - if (!shouldRun || !pluginInfos || !controllerConfigPath || !controllerConfig) { + if (!shouldRun || !pluginInfos || !controllerConfig) { return; } controller = new Controller( clusterLogger, pluginInfos, - controllerConfigPath, controllerConfig, Boolean(args.canRestart), Boolean(args.recovery), diff --git a/packages/controller/src/Controller.ts b/packages/controller/src/Controller.ts index 925afe93..0c80fa80 100644 --- a/packages/controller/src/Controller.ts +++ b/packages/controller/src/Controller.ts @@ -50,7 +50,6 @@ export default class Controller { clusterLogger: winston.Logger; /** Array of plugin info objects for known plugins */ pluginInfos: lib.PluginNodeEnvInfo[]; - configPath: string; /** Controller config. */ config: lib.ControllerConfig; app: Application; @@ -155,7 +154,6 @@ export default class Controller { constructor( clusterLogger: winston.Logger, pluginInfos: lib.PluginNodeEnvInfo[], - configPath: string, config: lib.ControllerConfig, /** @@ -184,7 +182,6 @@ export default class Controller { ) { this.clusterLogger = clusterLogger; this.pluginInfos = pluginInfos; - this.configPath = configPath; this.config = config; this.app = express(); @@ -586,7 +583,7 @@ export default class Controller { private async _saveDataInternal() { if (this.config.dirty) { this.config.dirty = false; - await lib.safeOutputFile(this.configPath, JSON.stringify(this.config, null, "\t")); + await lib.safeOutputFile(this.config.filepath!, JSON.stringify(this.config, null, "\t")); } await Promise.all([ diff --git a/packages/ctl/ctl.ts b/packages/ctl/ctl.ts index 70d49964..8945b500 100755 --- a/packages/ctl/ctl.ts +++ b/packages/ctl/ctl.ts @@ -209,16 +209,17 @@ async function startControl() { .parse() as CtlArguments ; - logger.verbose(`Loading config from ${args.config}`); let controlConfig; + const controlConfigPath = args.config; + logger.verbose(`Loading config from ${controlConfigPath}`); try { - const jsonConfig = JSON.parse(await fs.readFile(args.config, "utf8")); - controlConfig = lib.ControlConfig.fromJSON(jsonConfig, "control"); + const jsonConfig = JSON.parse(await fs.readFile(controlConfigPath, "utf8")); + controlConfig = lib.ControlConfig.fromJSON(jsonConfig, "control", controlConfigPath); } catch (err: any) { if (err.code === "ENOENT") { logger.verbose("Config not found, initializing new config"); - controlConfig = new lib.ControlConfig("control"); + controlConfig = new lib.ControlConfig("control", undefined, controlConfigPath); } else { throw new lib.StartupError(`Failed to load ${args.config}: ${err.message}`); @@ -232,7 +233,7 @@ async function startControl() { // Handle the control-config command before trying to connect. if (args._[0] === "control-config") { - await lib.handleConfigCommand(args, controlConfig, args.config); + await lib.handleConfigCommand(args, controlConfig); return; } diff --git a/packages/host/host.ts b/packages/host/host.ts index 716c874f..c40df9f1 100755 --- a/packages/host/host.ts +++ b/packages/host/host.ts @@ -167,16 +167,17 @@ async function startHost() { lib.registerPluginMessages(pluginInfos); lib.addPluginConfigFields(pluginInfos); - logger.info(`Loading config from ${args.config}`); let hostConfig; + const hostConfigPath = args.config; + logger.info(`Loading config from ${hostConfigPath}`); try { - const jsonConfig = JSON.parse(await fs.readFile(args.config, "utf8")); - hostConfig = lib.HostConfig.fromJSON(jsonConfig, "host"); + const jsonConfig = JSON.parse(await fs.readFile(hostConfigPath, "utf8")); + hostConfig = lib.HostConfig.fromJSON(jsonConfig, "host", hostConfigPath); } catch (err: any) { if (err.code === "ENOENT") { logger.info("Config not found, initializing new config"); - hostConfig = new lib.HostConfig("host"); + hostConfig = new lib.HostConfig("host", undefined, hostConfigPath); } else { throw new lib.StartupError(`Failed to load ${args.config}: ${err.message}`); @@ -186,7 +187,7 @@ async function startHost() { hostConfig.set("host.version", version); // Allows tracking last loaded version if (command === "config") { - await lib.handleConfigCommand(args, hostConfig, args.config); + await lib.handleConfigCommand(args, hostConfig); return; } @@ -229,7 +230,6 @@ async function startHost() { let hostConnector = new HostConnector(hostConfig, tlsCa, pluginInfos); host = new Host( hostConnector, - args.config, hostConfig, tlsCa, pluginInfos, diff --git a/packages/host/src/Host.ts b/packages/host/src/Host.ts index 8dfddad9..13616d29 100644 --- a/packages/host/src/Host.ts +++ b/packages/host/src/Host.ts @@ -48,14 +48,14 @@ async function discoverInstances(instancesDir: string) { try { const jsonConfig = JSON.parse(await fs.readFile(configPath, "utf8")); - instanceConfig = lib.InstanceConfig.fromJSON(jsonConfig, "host"); + instanceConfig = lib.InstanceConfig.fromJSON(jsonConfig, "host", configPath); } catch (err: any) { if (err.code === "ENOENT") { continue; // Ignore folders without config.json } - logger.error(`Error occured while parsing ${configPath}: ${err.message}`); + logger.error(`Error occurred while parsing ${configPath}: ${err.message}`); continue; } @@ -258,7 +258,6 @@ export default class Host extends lib.Link { */ tlsCa?: string; pluginInfos: lib.PluginNodeEnvInfo[]; - configPath: string; config: lib.HostConfig; /** Mapping of plugin name to loaded plugin */ @@ -289,7 +288,6 @@ export default class Host extends lib.Link { constructor( connector: HostConnector, - hostConfigPath: string, hostConfig: lib.HostConfig, tlsCa: string | undefined, pluginInfos: lib.PluginNodeEnvInfo[], @@ -309,7 +307,6 @@ export default class Host extends lib.Link { this.tlsCa = tlsCa; this.pluginInfos = pluginInfos; - this.configPath = hostConfigPath; this.config = hostConfig; this.config.on("fieldChanged", (name, curr, prev) => { @@ -659,6 +656,7 @@ export default class Host extends lib.Link { instanceConfig.update(config, false, "controller"); let instanceDir = await this._createNewInstanceDir(instanceConfig.get("instance.name")); + instanceConfig.filepath = path.join(instanceDir, "instance.json"); logger.info(`Creating ${instanceDir}`); await Instance.populate_folders(instanceDir); @@ -701,10 +699,7 @@ export default class Host extends lib.Link { _warning: "Changes to this file will be overwritten by the controller's copy.", ...instanceInfo.config.toJSON(), }; - await lib.safeOutputFile( - path.join(instanceInfo.path, "instance.json"), - JSON.stringify(warnedOutput, null, "\t") - ); + await lib.safeOutputFile(instanceInfo.config.filepath!, JSON.stringify(warnedOutput, null, "\t")); } async handleInstanceUnassignInternalRequest(request: lib.InstanceUnassignInternalRequest) { @@ -1157,7 +1152,7 @@ export default class Host extends lib.Link { } logger.info("Saving config"); - await lib.safeOutputFile(this.configPath, JSON.stringify(this.config, null, "\t")); + await lib.safeOutputFile(this.config.filepath!, JSON.stringify(this.config, null, "\t")); for (const instanceInfo of this.instanceInfos.values()) { // Save in case host_assigned_game_port changed in the config. await this.saveInstanceConfig(instanceInfo); diff --git a/packages/lib/src/config/classes.ts b/packages/lib/src/config/classes.ts index ab0b056d..dd6c663d 100644 --- a/packages/lib/src/config/classes.ts +++ b/packages/lib/src/config/classes.ts @@ -201,8 +201,9 @@ export class Config< /** Defines the default access locations for the config class */ static defaultAccess: ConfigLocation[] = ["controller", "host", "control"]; - fields: Fields; - _unknownFields: Record = {}; + /** Contains the fields and their values */ + private fields: Fields; + private _unknownFields: Record = {}; /** Set to true when a field in the config is changed. */ dirty = false; @@ -213,13 +214,16 @@ export class Config< * Create a new instance of the given config * * @param location - - * Location to evaluate access for this instance from + * Location to evaluate access for this instance from. * @param fields - * Serialized representation of fields to load. + * @param filepath - + * Filepath for the config to save to. */ constructor( public location: ConfigLocation, fields?: Static, + public filepath?: string, ) { if (typeof location !== "string") { throw new Error("location must be a string"); @@ -306,11 +310,13 @@ export class Config< * * @param json - Serialized config to load. * @param location - Location used for access control. + * @param filepath - Filepath for the config to save to. * @returns Instance of this config */ static fromJSON( json: Static, location: ConfigLocation, + filepath?: string, ) { // migrate: Pre alpha 14 config format if (typeof json === "object" && json !== null && json.groups instanceof Array) { @@ -326,7 +332,7 @@ export class Config< if (!valid) { throw new Error("Invalid config"); } - return new this(location, json); + return new this(location, json, filepath); } toJSON() { diff --git a/packages/lib/src/config/definitions.ts b/packages/lib/src/config/definitions.ts index b05123f1..f6497df2 100644 --- a/packages/lib/src/config/definitions.ts +++ b/packages/lib/src/config/definitions.ts @@ -3,6 +3,8 @@ import * as classes from "./classes"; import type { PluginNodeEnvInfo, PluginWebpackEnvInfo } from "../plugin"; import { Static } from "@sinclair/typebox"; +type configFromJSON = (...args: Parameters) => T; + export interface ControllerConfigFields { "controller.version": string; "controller.name": string; @@ -36,7 +38,7 @@ export interface ControllerConfigFields { * @extends classes.Config */ export class ControllerConfig extends classes.Config { - declare static fromJSON: (json: classes.ConfigSchema, location: classes.ConfigLocation) => ControllerConfig; + declare static fromJSON: configFromJSON; static migrations(config: Static) { if (config.hasOwnProperty("controller.external_address")) { config["controller.public_url"] = config["controller.external_address"]; @@ -243,7 +245,7 @@ export interface HostConfigFields { * @extends classes.Config */ export class HostConfig extends classes.Config { - declare static fromJSON: (json: classes.ConfigSchema, location: classes.ConfigLocation) => HostConfig; + declare static fromJSON: configFromJSON; static fieldDefinitions: classes.ConfigDefs = { "host.version": { type: "string", @@ -375,7 +377,7 @@ export interface InstanceConfigFields { * @extends classes.Config */ export class InstanceConfig extends classes.Config { - declare static fromJSON: (json: classes.ConfigSchema, location: classes.ConfigLocation) => InstanceConfig; + declare static fromJSON: configFromJSON; static migrations(config: Static) { function boolToEnableDisable(name: string) { if (config.hasOwnProperty(name) && typeof config[name] === "boolean") { @@ -568,7 +570,7 @@ export interface ControlConfigFields { * @extends classes.Config */ export class ControlConfig extends classes.Config { - declare static fromJSON: (json: classes.ConfigSchema, location: classes.ConfigLocation) => ControlConfig; + declare static fromJSON: configFromJSON; static fieldDefinitions: classes.ConfigDefs = { "control.controller_url": { description: "URL to connect to the controller at", diff --git a/packages/lib/src/shared_commands.ts b/packages/lib/src/shared_commands.ts index 13be8c32..6197bf52 100644 --- a/packages/lib/src/shared_commands.ts +++ b/packages/lib/src/shared_commands.ts @@ -135,7 +135,6 @@ export function configCommand(yargs: any) { export async function handleConfigCommand( args: Record, instance: libConfig.Config, - configPath: string ) { let command = (args._ as string[])[1]; @@ -165,7 +164,7 @@ export async function handleConfigCommand( try { instance.set(args.field as string, args.value); - await libFileOps.safeOutputFile(configPath, JSON.stringify(instance, null, "\t")); + await libFileOps.safeOutputFile(instance.filepath!, JSON.stringify(instance, null, "\t")); } catch (err) { if (err instanceof libConfig.InvalidField || err instanceof libConfig.InvalidValue) { logger.error(err.message); diff --git a/test/controller/Controller.js b/test/controller/Controller.js index 44ce9b11..1cfc4814 100644 --- a/test/controller/Controller.js +++ b/test/controller/Controller.js @@ -28,7 +28,7 @@ describe("controller/src/Controller", function() { /** @type {Controller} */ let controller, mockInstanceConfig; before(async function() { - controller = new Controller({}, [], "", new ControllerConfig("controller")); + controller = new Controller({}, [], new ControllerConfig("controller")); mockInstanceConfig = new MockInstanceConfig(new Map([ ["instance.id", 100], ["instance.name", "test"], ["factorio.settings", []], ])); diff --git a/test/controller/WsServer.js b/test/controller/WsServer.js index 46f0bd47..7501ec59 100644 --- a/test/controller/WsServer.js +++ b/test/controller/WsServer.js @@ -10,7 +10,7 @@ describe("controller/src/WsServer", function() { before(function() { let config = new ControllerConfig("controller"); config.set("controller.trusted_proxies", "127.0.0.0/8, ::1"); - let controller = new Controller({}, [], "", config); + let controller = new Controller({}, [], config); wsServer = controller.wsServer; }); describe(".remoteAddr()", function() { diff --git a/test/host.js b/test/host.js index f5189d0d..559da84f 100644 --- a/test/host.js +++ b/test/host.js @@ -10,13 +10,14 @@ const { _discoverInstances } = require("@clusterio/host/dist/node/src/Host"); describe("Host testing", function() { describe("discoverInstances()", function() { it("should discover test instance", async function() { - let instancePath = path.join("test", "file", "instances"); - let instanceInfos = await _discoverInstances(instancePath); + const instancePath = path.join("test", "file", "instances"); + const instanceInfos = await _discoverInstances(instancePath); - let referenceConfig = new lib.InstanceConfig("host"); - referenceConfig.set("instance.id", 1); - referenceConfig.set("instance.name", "test"); - referenceConfig.dirty = false; + const configPath = path.join(instancePath, "test", "instance.json"); + const referenceConfig = new lib.InstanceConfig("host", { + "instance.id": 1, + "instance.name": "test", + }, configPath); assert.deepEqual(instanceInfos, new Map([ [1, { diff --git a/test/integration/routing.js b/test/integration/routing.js index f2b17d34..827e6103 100644 --- a/test/integration/routing.js +++ b/test/integration/routing.js @@ -23,7 +23,7 @@ function connectHost(controller, hostId, plugins) { controller.wsServer.hostConnections.set(hostId, hostConnection); const hostConfig = new lib.HostConfig("host"); hostConfig.set("host.id", hostId); - return new Host(hostSide, "invalid", hostConfig, undefined, []); + return new Host(hostSide, hostConfig, undefined, []); } /** @@ -107,12 +107,7 @@ describe("Integration of link routing", function() { let controlB; beforeEach(function() { - controller = new Controller( - {}, - [], - "invalid", - new lib.ControllerConfig("controller") - ); + controller = new Controller({}, [], new lib.ControllerConfig("controller")); hostA = connectHost(controller, 10, []); hostB = connectHost(controller, 20, []); instanceA1 = connectInstance(controller, hostA, 11);