fix(s3): starting a member's workspace makes its missing folder, as the member
v2.7.3's ENOCWD left a new member no web path to a first project: their home is no workspace (EFOLDER) and a not-yet-existing folder could not start. run-as.makeMemberFolder: mkdir -p through write-as, AS the member, only inside their own home (never the home itself, never a host folder, never for a member with no account). Test: server-s3-lanes.
This commit is contained in:
@@ -173,3 +173,22 @@ describe("an analyst's workspace is theirs, like a developer's", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("a member's new project folder", () => {
|
||||||
|
it("is made as the member, and nothing else is made for anyone", () => {
|
||||||
|
const { fakeSudo } = require("./fixtures/fake-sudo");
|
||||||
|
const sudo = fakeSudo();
|
||||||
|
runAs.__setExecImpl(sudo.exec);
|
||||||
|
const fresh = path.join(USERS, "mia", "brand-new", "sub");
|
||||||
|
assert.equal(runAs.makeMemberFolder(fresh), true);
|
||||||
|
assert.ok(fs.statSync(fresh).isDirectory());
|
||||||
|
const call = sudo.calls.find((c) => c.includes("/opt/agenthub/libexec/write-as"));
|
||||||
|
assert.ok(call && call.includes("ah-mia") && call.includes(fresh) && call.includes("mkdir"));
|
||||||
|
sudo.calls.length = 0;
|
||||||
|
assert.equal(runAs.makeMemberFolder(path.join(USERS, "mia")), false, "the home itself");
|
||||||
|
assert.equal(runAs.makeMemberFolder(path.join(TMP, "host-new")), false, "not a member's");
|
||||||
|
assert.equal(runAs.makeMemberFolder(path.join(USERS, "nobody", "x")), false, "no account");
|
||||||
|
assert.deepEqual(sudo.calls, []);
|
||||||
|
assert.equal(fs.existsSync(path.join(TMP, "host-new")), false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -488,6 +488,26 @@ function sameOwner(...paths) {
|
|||||||
|
|
||||||
/** `mkdir -p` / `rm -rf` of a path in a member's home, AS the member. */
|
/** `mkdir -p` / `rm -rf` of a path in a member's home, AS the member. */
|
||||||
const mkdirAs = (acct, dir) => writeAs(acct, dir, "mkdir");
|
const mkdirAs = (acct, dir) => writeAs(acct, dir, "mkdir");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* S3: make a missing workspace folder inside a member's own home, AS them
|
||||||
|
* (`mkdir -p`, so only where they could themselves). A new member has no
|
||||||
|
* other way to their first project: their home is no workspace (EFOLDER).
|
||||||
|
* False — nothing made — for any other folder, the home itself, or a member
|
||||||
|
* with no usable account.
|
||||||
|
*/
|
||||||
|
function makeMemberFolder(dir) {
|
||||||
|
let acct;
|
||||||
|
try {
|
||||||
|
acct = accountForPath(dir);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const abs = path.resolve(dir);
|
||||||
|
if (!acct || abs === acct.home) return false;
|
||||||
|
mkdirAs(acct, abs);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const removeAs = (acct, p) => writeAs(acct, p, "remove");
|
const removeAs = (acct, p) => writeAs(acct, p, "remove");
|
||||||
|
|
||||||
/** `[cmd, argv]` for `command args` in `cwd`: as its member, else as is. */
|
/** `[cmd, argv]` for `command args` in `cwd`: as its member, else as is. */
|
||||||
@@ -528,6 +548,7 @@ module.exports = {
|
|||||||
handoffWrite,
|
handoffWrite,
|
||||||
writeAs,
|
writeAs,
|
||||||
mkdirAs,
|
mkdirAs,
|
||||||
|
makeMemberFolder,
|
||||||
removeAs,
|
removeAs,
|
||||||
launchArgv,
|
launchArgv,
|
||||||
sameOwner,
|
sameOwner,
|
||||||
|
|||||||
@@ -2078,7 +2078,9 @@ router.post("/:id/:action", sameOriginGuard, async (req, res) => {
|
|||||||
if (!fs.statSync(lane.cwd).isDirectory())
|
if (!fs.statSync(lane.cwd).isDirectory())
|
||||||
throw Object.assign(new Error(), { code: "ENOTDIR" });
|
throw Object.assign(new Error(), { code: "ENOTDIR" });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === "ENOENT" || err.code === "ENOTDIR") {
|
// A member's new project folder is made for them, as them.
|
||||||
|
const made = err.code === "ENOENT" && require("../lib/run-as").makeMemberFolder(lane.cwd);
|
||||||
|
if (!made && (err.code === "ENOENT" || err.code === "ENOTDIR")) {
|
||||||
return res.status(409).json({
|
return res.status(409).json({
|
||||||
error: {
|
error: {
|
||||||
code: "ENOCWD",
|
code: "ENOCWD",
|
||||||
|
|||||||
Reference in New Issue
Block a user