Add lockfile bypass

This commit is contained in:
Cooldude2606 committed 2026-01-08 22:48:00 +00:00
1 parent bccb6527a7
commit d48b731ea4
6 files changed
+148 -21

No files matched your search

+8 -2
View File
@@ -255,6 +255,7 @@ async function initialize(): Promise<InitializeParameters> {
default: "plugin-list.json",
type: "string",
})
.option("bypass-lock-file", { hidden: true, type: "boolean", nargs: 0, default: false })
.command("plugin", "Manage available plugins", lib.pluginCommand)
.command("config", "Manage Controller config", lib.configCommand)
.command("bootstrap", "Bootstrap access to cluster", yargs => {
@@ -355,9 +356,14 @@ async function initialize(): Promise<InitializeParameters> {
lib.registerPluginMessages(pluginInfos);
lib.addPluginConfigFields(pluginInfos);
let controllerConfig;
const controllerConfigPath = args.config;
const controllerConfigLock = new lib.LockFile(`${controllerConfigPath}.lock`);
const controllerConfigLockPath = `${controllerConfigPath}.lock`;
if (args.bypassLockFile) {
await fs.unlink(controllerConfigLockPath).catch(() => {}); // ignore error, file might not exist
}
let controllerConfig;
const controllerConfigLock = new lib.LockFile(controllerConfigLockPath);
logger.info(`Loading config from ${controllerConfigPath}`);
try {
controllerConfig = await lib.ControllerConfig.fromFile("controller", controllerConfigPath);
+8 -2
View File
@@ -189,6 +189,7 @@ export async function initialize(
default: "plugin-list.json",
type: "string",
})
.option("bypass-lock-file", { hidden: true, type: "boolean", nargs: 0, default: false })
.command("plugin", "Manage available plugins", lib.pluginCommand)
.command("control-config", "Manage Control config", lib.configCommand)
.wrap(parser.terminalWidth())
@@ -234,9 +235,14 @@ export async function initialize(
.parse(argv) as CtlArguments
;
let controlConfig;
const controlConfigPath = args.config;
const controlConfigLock = new lib.LockFile(`${controlConfigPath}.lock`);
const controlConfigLockPath = `${controlConfigPath}.lock`;
if (args.bypassLockFile) {
await fs.unlink(controlConfigLockPath).catch(() => {}); // ignore error, file might not exist
}
let controlConfig;
const controlConfigLock = new lib.LockFile(controlConfigLockPath);
logger.verbose(`Loading config from ${controlConfigPath}`);
try {
controlConfig = await lib.ControlConfig.fromFile("control", controlConfigPath);
+8 -2
View File
@@ -113,6 +113,7 @@ async function startHost() {
default: "plugin-list.json",
type: "string",
})
.option("bypass-lock-file", { hidden: true, type: "boolean", nargs: 0, default: false })
.command("plugin", "Manage available plugins", lib.pluginCommand)
.command("config", "Manage Host config", lib.configCommand)
.command("run", "Run host", yargs => {
@@ -167,9 +168,14 @@ async function startHost() {
lib.registerPluginMessages(pluginInfos);
lib.addPluginConfigFields(pluginInfos);
let hostConfig;
const hostConfigPath = args.config;
const hostConfigLock = new lib.LockFile(`${hostConfigPath}.lock`);
const hostConfigLockPath = `${hostConfigPath}.lock`;
if (args.bypassLockFile) {
await fs.unlink(hostConfigLockPath).catch(() => {}); // ignore error, file might not exist
}
let hostConfig;
const hostConfigLock = new lib.LockFile(hostConfigLockPath);
logger.info(`Loading config from ${hostConfigPath}`);
try {
hostConfig = await lib.HostConfig.fromFile("host", hostConfigPath);
+112 -11
View File
@@ -14,7 +14,7 @@ const testStrings = require("../lib/factorio/test_strings");
const {
TestControl, TestControlConnector, url, controlToken, slowTest,
execCtl, execCtlProcess, execController, execHost, sendRcon, getControl,
spawnNode, instancesDir, factorioDir,
spawnNode, instancesDir, factorioDir, databaseDir, controllerConfigPath,
} = require("./index");
@@ -120,22 +120,46 @@ describe("Integration of Clusterio", function() {
});
describe("bootstrap create-admin", function() {
it("refuses to modify the database files", async function() {
it("should refuse to modify the database files when locked", async function() {
await assert.rejects(
execController("bootstrap create-admin BootstrapAdminTest")
);
});
it("should modify the database files when locked if bypass option is given", async function() {
const lockFilePath = `${controllerConfigPath}.lock`;
await fs.copyFile(lockFilePath, "temp.lock");
try {
await execController("bootstrap create-admin BootstrapAdminTest2 --bypass-lock-file");
} finally {
await fs.move("temp.lock", lockFilePath); // Replace the lockfile after it is deleted
}
const json = await fs.readJSON(path.join(databaseDir, "users.json"));
assert.equal(Object.values(json).some(user => user.name === "BootstrapAdminTest2"), true);
});
});
describe("config", function() {
it("can read the config file", async function() {
await execController("config list");
});
it("refuses to modify the config files", async function() {
it("should refuse to modify the config file while locked", async function() {
await assert.rejects(
execController("confit set controller.name ConfigEditTest")
execController("config set controller.name ConfigEditTest")
);
});
it("should modify the config file while locked if bypass option is given", async function() {
const lockFilePath = `${controllerConfigPath}.lock`;
await fs.copyFile(lockFilePath, "temp.lock");
try {
await execController("config set controller.name ConfigEditTest2 --bypass-lock-file");
} finally {
await fs.move("temp.lock", lockFilePath); // Replace the lockfile after it is deleted
}
const json = await fs.readJSON(path.join(controllerConfigPath));
assert.equal(json["controller.name"], "ConfigEditTest2");
});
});
describe("run", function() {
@@ -162,9 +186,38 @@ describe("Integration of Clusterio", function() {
await events.once(connectorB, "resume");
await connectorB.close(1000, "");
});
it("refuses to start a second process", async function() {
it("should refuse to start a second process while locked", async function() {
await assert.rejects(execController("run"));
});
it("should start a second process while locked if bypass option is given", async function() {
slowTest(this);
const lockFilePath = `${controllerConfigPath}.lock`;
await fs.copyFile(lockFilePath, "temp.lock");
try {
await execController("run --bypass-lock-file");
} catch (err) {
assert.equal(/Error: Server listening failed/.test(err.stderr), true);
}
await fs.move("temp.lock", lockFilePath); // Replace the lockfile after it is deleted
});
it("should refuse to start when no users are loaded", async function() {
const dir = path.join("temp", "test", "empty_controller");
await fs.emptyDir(dir);
await assert.rejects(execController("run", { cwd: dir }));
});
it("should start when no users are loaded if bypass option given", async function() {
slowTest(this);
const dir = path.join("temp", "test", "empty_controller");
await fs.emptyDir(dir);
const child = await spawnNode(
"altController",
"../../../packages/controller run --no-check-user-count",
/Started controller/,
{ cwd: dir },
);
assert.ok(child.kill());
assert.equal(child.killed, true);
});
});
describe("queryLogRequestHandler", function() {
@@ -271,14 +324,62 @@ describe("Integration of Clusterio", function() {
it("can read the config file", async function() {
await execHost("config list");
});
it("refuses to modify the config files", async function() {
await assert.rejects(
execHost("confit set host.name ConfigEditTest")
);
it("should refuse to modify the config file while locked", async function() {
slowTest(this);
let hostProcess; // Alt host used since the process could get killed
const config = "alt-host-config.json";
try {
hostProcess = await startAltHost();
await assert.rejects(
execHost(`--config ${config} config set host.name ConfigEditTest`)
);
} finally {
await stopAltHost(hostProcess);
}
});
it("should modify the config file while locked if bypass option is given", async function() {
slowTest(this);
let hostProcess; // Alt host used since the process could get killed
const config = "alt-host-config.json";
try {
hostProcess = await startAltHost();
await execHost(`--config ${config} config set host.name ConfigEditTest2 --bypass-lock-file`);
} finally {
await stopAltHost(hostProcess);
}
const json = await fs.readJSON(path.join("temp", "test", config));
assert.equal(json["host.name"], "ConfigEditTest2");
});
});
it("refuses to start a second process", async function() {
await assert.rejects(execHost("run"));
describe("run", function() {
it("should refuse to start a second process while locked", async function() {
slowTest(this);
let hostProcess; // Alt host used since the process could get killed
const config = "alt-host-config.json";
try {
hostProcess = await startAltHost();
await assert.rejects(
execHost(`--config ${config} run`)
);
} finally {
await stopAltHost(hostProcess);
}
});
it("should start a second process while locked if bypass option is given", async function() {
slowTest(this);
let hostProcessA, hostProcessB; // Alt host used since the process could get killed
const config = "alt-host-config.json";
try {
hostProcessA = await startAltHost();
hostProcessB = await spawnNode(
"alt-host-b:", `../../packages/host run --config ${config} --bypass-lock-file`, /Started host/
);
} finally {
await stopAltHost(hostProcessA);
await stopAltHost(hostProcessB);
}
});
});
});
+5 -4
View File
@@ -229,7 +229,8 @@ function getControl() {
return control;
}
function spawn(name, cmd, waitFor) {
/** @returns {Promise<child_process.ChildProcess>} */
function spawn(name, cmd, waitFor, options = {}) {
const silent = process.env.SILENT_TEST;
const bootstrap = !controllerProcess || !hostProcess;
@@ -242,7 +243,7 @@ function spawn(name, cmd, waitFor) {
return new Promise((resolve, reject) => {
log(cmd);
let parts = cmd.split(" ");
let process = child_process.spawn(parts[0], parts.slice(1), { cwd: path.join("temp", "test") });
let process = child_process.spawn(parts[0], parts.slice(1), { cwd: path.join("temp", "test"), ...options });
let stdout = new LineSplitter({ readableObjectMode: true });
let stderr = new LineSplitter({ readableObjectMode: true });
let onDataOut = line => {
@@ -266,8 +267,8 @@ function spawn(name, cmd, waitFor) {
});
}
async function spawnNode(name, cmd, waitFor) {
return await spawn(name, `node --enable-source-maps ${cmd}`, waitFor);
function spawnNode(name, cmd, waitFor, options) {
return spawn(name, `node --enable-source-maps ${cmd}`, waitFor, options);
}
before(async function() {
+7
View File
@@ -79,6 +79,13 @@ describe("class LockFile", function () {
const held = await lock.isHeld();
assert.equal(held, false);
});
it("should return false if lock file was deleted while held", async function() {
await lock.acquire();
await fs.unlink(filePath);
const held = await lock.isHeld();
assert.equal(held, false);
});
});
describe("isStale()", function () {