Give mounts/blockDevices their own module, cleanup
parent
52e4d441e9
commit
da472314dc
@ -0,0 +1,117 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const DataLoader = require("dataloader");
|
||||||
|
const fs = require("fs").promises;
|
||||||
|
const matchValue = require("match-value");
|
||||||
|
const memoizee = require("memoizee");
|
||||||
|
const unreachable = require("@joepie91/unreachable")("@sysquery/block-devices");
|
||||||
|
|
||||||
|
// TODO: Refactor dlayerSource to be object-mergeable instead of all-encompassing
|
||||||
|
const dlayerSource = require("../dlayer-source");
|
||||||
|
const All = require("../graphql-interface/symbols/all");
|
||||||
|
const lsblk = require("../exec-lsblk");
|
||||||
|
const mapFromSource = require("../map-from-source");
|
||||||
|
const treeMapAsync = require("../tree-map-async");
|
||||||
|
const treeFind = require("../tree-find");
|
||||||
|
|
||||||
|
function makePredicate({ path, name }) {
|
||||||
|
if (path != null) {
|
||||||
|
return (device) => device.path === path;
|
||||||
|
} else if (name != null) {
|
||||||
|
return (device) => device.name === name;
|
||||||
|
} else {
|
||||||
|
unreachable("No selector specified for lsblk");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: "sysquery.blockDevice",
|
||||||
|
makeContext: function () {
|
||||||
|
let lsblkOnce = memoizee(async () => {
|
||||||
|
return treeMapAsync(await lsblk(), async (device) => {
|
||||||
|
return {
|
||||||
|
... device,
|
||||||
|
path: await fs.realpath(device.path)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
lsblk: new DataLoader(async function (selectors) {
|
||||||
|
let blockDeviceTree = await lsblkOnce();
|
||||||
|
|
||||||
|
return selectors.map((selector) => {
|
||||||
|
if (selector === All) {
|
||||||
|
return blockDeviceTree;
|
||||||
|
} else {
|
||||||
|
return treeFind(blockDeviceTree, makePredicate(selector));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
};
|
||||||
|
},
|
||||||
|
types: {
|
||||||
|
"sysquery.blockDevices.BlockDevice": function ({ name, path }) {
|
||||||
|
return dlayerSource.withSources({
|
||||||
|
$sources: {
|
||||||
|
lsblk: {
|
||||||
|
[dlayerSource.ID]: { name, path },
|
||||||
|
name: "name",
|
||||||
|
path: (device) => fs.realpath(device.path),
|
||||||
|
type: (device) => matchValue(device.type, {
|
||||||
|
partition: "PARTITION",
|
||||||
|
disk: "DISK",
|
||||||
|
loopDevice: "LOOP_DEVICE"
|
||||||
|
}),
|
||||||
|
size: "size",
|
||||||
|
mountpoint: "mountpoint", // FIXME: Isn't this obsoleted by `mounts`?
|
||||||
|
deviceNumber: "deviceNumber",
|
||||||
|
removable: "removable",
|
||||||
|
readOnly: "readOnly",
|
||||||
|
children: (device, { $make }) => device.children.map((child) => {
|
||||||
|
return $make("sysquery.blockDevices.BlockDevice", { name: child.name });
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
extensions: {
|
||||||
|
"sysquery.mounts.Mount": {
|
||||||
|
sourceDevice: async function (_, { lsblk, $make, $getProperty }) {
|
||||||
|
let sourceDevicePath = await $getProperty(this, "sourceDevicePath");
|
||||||
|
|
||||||
|
if (sourceDevicePath == null) {
|
||||||
|
// This occurs when the mount is not backed by a device, eg. an sshfs FUSE mount
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
let realSourcePath = await fs.realpath(sourceDevicePath);
|
||||||
|
|
||||||
|
if (await lsblk.load({ path: realSourcePath }) != null) {
|
||||||
|
return $make("sysquery.blockDevices.BlockDevice", { path: realSourcePath });
|
||||||
|
} else {
|
||||||
|
// This occurs when the `sourceDevice` is a valid device, but it is not a *block* device, eg. `/dev/fuse`
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
root: {
|
||||||
|
resources: {
|
||||||
|
blockDevices: ({ names, paths }, { lsblk, $make }) => {
|
||||||
|
// TODO: Design better abstraction for this sort of case
|
||||||
|
let selectors = (names == null && paths == null)
|
||||||
|
? null
|
||||||
|
: [
|
||||||
|
... (names ?? []).map((name) => ({ name: name })),
|
||||||
|
... (paths ?? []).map((path) => ({ path: path }))
|
||||||
|
];
|
||||||
|
|
||||||
|
return mapFromSource(lsblk, selectors, (device) => {
|
||||||
|
return $make("sysquery.blockDevices.BlockDevice", { name: device.name });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -1,37 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
const memoizee = require("memoizee");
|
|
||||||
const fs = require("fs").promises;
|
|
||||||
const findmnt = require("../exec-findmnt");
|
|
||||||
const All = require("../graphql-interface/symbols/all");
|
|
||||||
const treeMapAsync = require("../tree-map-async");
|
|
||||||
const treeFind = require("../tree-find");
|
|
||||||
|
|
||||||
module.exports = function () {
|
|
||||||
let findmntOnce = memoizee(async () => {
|
|
||||||
return treeMapAsync(await findmnt(), async (mount) => {
|
|
||||||
return {
|
|
||||||
... mount,
|
|
||||||
sourceDevice: (mount.sourceDevice?.startsWith("/"))
|
|
||||||
? await fs.realpath(mount.sourceDevice)
|
|
||||||
: mount.sourceDevice
|
|
||||||
};
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return async function (mountpoints) {
|
|
||||||
let mounts = await findmntOnce();
|
|
||||||
|
|
||||||
// TODO: It's kind of strange that it sometimes returns a tree and sometimes a list, this can probably be improved?
|
|
||||||
let matches = mountpoints.map((mountpoint) => {
|
|
||||||
if (mountpoint === All) {
|
|
||||||
return mounts;
|
|
||||||
} else {
|
|
||||||
return treeFind(mounts, (mount) => mount.mountpoint === mountpoint);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return matches;
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,152 +1,17 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const fs = require("fs").promises;
|
|
||||||
const matchValue = require("match-value");
|
|
||||||
const DataLoader = require("dataloader");
|
|
||||||
|
|
||||||
const mapFromSource = require("../map-from-source");
|
|
||||||
const dlayerSource = require("../dlayer-source");
|
|
||||||
const All = require("../graphql-interface/symbols/all");
|
|
||||||
const treeFilterFlatAsync = require("../tree-filter-flat-async");
|
|
||||||
const assert = require("assert");
|
|
||||||
|
|
||||||
// FIXME: Simplify findmnt/lsblk source definitions, and probably separate out mounts and block devices into their own separate modules
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name: "sysquery.core",
|
name: "sysquery.core",
|
||||||
makeContext: function () {
|
makeContext: function () {
|
||||||
// MARKER: Complete sources migration, test, move smartctl
|
return {};
|
||||||
return {
|
|
||||||
lsblk: new DataLoader(require("./lsblk")()),
|
|
||||||
findmnt: new DataLoader(require("./findmnt")())
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
types: {
|
types: {
|
||||||
"sysquery.core.Mount": function ({ mountpoint }) {
|
// None
|
||||||
return dlayerSource.withSources({
|
|
||||||
mountpoint: mountpoint,
|
|
||||||
sourceDevice: async (_, { lsblk, findmnt, $make }) => {
|
|
||||||
let mount = await findmnt.load(mountpoint);
|
|
||||||
|
|
||||||
if (mount == null) {
|
|
||||||
// TODO: Can this ever happen for any legitimate reason?
|
|
||||||
throw new Error(`Mountpoint '${mountpoint}' not found in findmnt output`);
|
|
||||||
} else if (mount.sourceDevice == null) {
|
|
||||||
// This occurs when the mount is not backed by a device, eg. an sshfs FUSE mount
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
let sourcePath = await fs.realpath(mount.sourceDevice);
|
|
||||||
|
|
||||||
if (await lsblk.load({ path: sourcePath }) != null) {
|
|
||||||
return $make("sysquery.core.BlockDevice", { path: sourcePath });
|
|
||||||
} else {
|
|
||||||
// This occurs when the `sourceDevice` is a valid device, but it is not a *block* device, eg. `/dev/fuse`
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
$sources: {
|
|
||||||
findmnt: {
|
|
||||||
[dlayerSource.ID]: mountpoint,
|
|
||||||
id: "id",
|
|
||||||
// FIXME: Aren't we inferring the below somewhere else in the code, using the square brackets?
|
|
||||||
type: (mount) => (mount.rootPath === "/")
|
|
||||||
? "ROOT_MOUNT"
|
|
||||||
: "SUBMOUNT",
|
|
||||||
filesystem: "filesystem",
|
|
||||||
options: "options",
|
|
||||||
label: "label",
|
|
||||||
uuid: "uuid",
|
|
||||||
partitionLabel: "partitionLabel",
|
|
||||||
partitionUUID: "partitionUUID",
|
|
||||||
deviceNumber: "deviceNumber",
|
|
||||||
totalSpace: "totalSpace",
|
|
||||||
freeSpace: "freeSpace",
|
|
||||||
usedSpace: "usedSpace",
|
|
||||||
rootPath: "rootPath",
|
|
||||||
taskID: "taskID",
|
|
||||||
optionalFields: "optionalFields",
|
|
||||||
propagationFlags: "propagationFlags",
|
|
||||||
children: (mount, { $make }) => mount.children.map((child) => {
|
|
||||||
return $make("sysquery.core.Mount", { mountpoint: child.mountpoint });
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
"sysquery.core.BlockDevice": function ({ name, path }) {
|
|
||||||
return dlayerSource.withSources({
|
|
||||||
// TODO: Eventually make this produce a (filtered) tree instead?
|
|
||||||
mounts: async function ({ type }, { findmnt, $make, $getProperty, $getPropertyPath }) {
|
|
||||||
let mountTree = await findmnt.load(All);
|
|
||||||
|
|
||||||
let relevantMounts = await treeFilterFlatAsync(mountTree, async (mount) => {
|
|
||||||
let mountObject = $make("sysquery.core.Mount", { mountpoint: mount.mountpoint });
|
|
||||||
// console.log({ sourceDevice: await $getProperty(mountObject, "sourceDevice") });
|
|
||||||
let sourcePath = await $getPropertyPath(mountObject, "sourceDevice.path");
|
|
||||||
let sourceName = await $getPropertyPath(mountObject, "sourceDevice.name");
|
|
||||||
|
|
||||||
// TODO: This logic looks strange. Is it actually correct, even when only one of name/path is specified upon BlockDevice construction?
|
|
||||||
let matchesDevice = (
|
|
||||||
(sourcePath != null && sourcePath === path)
|
|
||||||
|| (sourceName != null && sourceName === name)
|
|
||||||
);
|
|
||||||
|
|
||||||
let matchesType = (
|
|
||||||
type == null
|
|
||||||
|| await $getProperty(mountObject, "type" === type)
|
|
||||||
);
|
|
||||||
|
|
||||||
return matchesDevice && matchesType;
|
|
||||||
}, { recurseFilteredSubtrees: true });
|
|
||||||
|
|
||||||
// This is a bit hacky; this approach should probably be replaced by a map-filter instead. But for now, this will do - as this all happens in the same request context, there's no real penalty to re-creating the mount objects a second time.
|
|
||||||
return relevantMounts.map((mount) => {
|
|
||||||
return $make("sysquery.core.Mount", { mountpoint: mount.mountpoint });
|
|
||||||
});
|
|
||||||
},
|
|
||||||
$sources: {
|
|
||||||
lsblk: {
|
|
||||||
[dlayerSource.ID]: { name, path },
|
|
||||||
name: "name",
|
|
||||||
path: (device) => fs.realpath(device.path),
|
|
||||||
type: (device) => matchValue(device.type, {
|
|
||||||
partition: "PARTITION",
|
|
||||||
disk: "DISK",
|
|
||||||
loopDevice: "LOOP_DEVICE"
|
|
||||||
}),
|
|
||||||
size: "size",
|
|
||||||
mountpoint: "mountpoint", // FIXME: Isn't this obsoleted by `mounts`?
|
|
||||||
deviceNumber: "deviceNumber",
|
|
||||||
removable: "removable",
|
|
||||||
readOnly: "readOnly",
|
|
||||||
children: (device, { $make }) => device.children.map((child) => {
|
|
||||||
return $make("sysquery.core.BlockDevice", { name: child.name });
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
extensions: {
|
extensions: {
|
||||||
// None
|
// None
|
||||||
},
|
},
|
||||||
root: {
|
root: {
|
||||||
resources: {
|
// None
|
||||||
blockDevices: ({ names, paths }, { lsblk, $make }) => {
|
|
||||||
// TODO: Design better abstraction for this sort of case
|
|
||||||
let selectors = (names == null && paths == null)
|
|
||||||
? null
|
|
||||||
: [
|
|
||||||
... (names ?? []).map((name) => ({ name: name })),
|
|
||||||
... (paths ?? []).map((path) => ({ path: path }))
|
|
||||||
];
|
|
||||||
|
|
||||||
return mapFromSource(lsblk, selectors, (device) => {
|
|
||||||
return $make("sysquery.core.BlockDevice", { name: device.name });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// FIXME: Add mounts
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
const memoizee = require("memoizee");
|
|
||||||
const fs = require("fs").promises;
|
|
||||||
const unreachable = require("@joepie91/unreachable")("cvm");
|
|
||||||
const lsblk = require("../exec-lsblk");
|
|
||||||
const All = require("../graphql-interface/symbols/all");
|
|
||||||
const treeFind = require("../tree-find");
|
|
||||||
const treeMapAsync = require("../tree-map-async");
|
|
||||||
|
|
||||||
function makePredicate({ path, name }) {
|
|
||||||
if (path != null) {
|
|
||||||
return (device) => device.path === path;
|
|
||||||
} else if (name != null) {
|
|
||||||
return (device) => device.name === name;
|
|
||||||
} else {
|
|
||||||
unreachable("No selector specified for lsblk");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = function () {
|
|
||||||
let lsblkOnce = memoizee(async () => {
|
|
||||||
return treeMapAsync(await lsblk(), async (device) => {
|
|
||||||
return {
|
|
||||||
... device,
|
|
||||||
path: await fs.realpath(device.path)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return async function (selectors) {
|
|
||||||
let blockDeviceTree = await lsblkOnce();
|
|
||||||
|
|
||||||
return selectors.map((selector) => {
|
|
||||||
if (selector === All) {
|
|
||||||
return blockDeviceTree;
|
|
||||||
} else {
|
|
||||||
return treeFind(blockDeviceTree, makePredicate(selector));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
};
|
|
@ -0,0 +1,115 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const fs = require("fs").promises;
|
||||||
|
const memoizee = require("memoizee");
|
||||||
|
const DataLoader = require("dataloader");
|
||||||
|
|
||||||
|
const findmnt = require("../exec-findmnt");
|
||||||
|
const dlayerSource = require("../dlayer-source");
|
||||||
|
const treeFilterFlatAsync = require("../tree-filter-flat-async");
|
||||||
|
const treeMapAsync = require("../tree-map-async");
|
||||||
|
const treeFind = require("../tree-find");
|
||||||
|
const All = require("../graphql-interface/symbols/all");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: "sysquery.mounts",
|
||||||
|
makeContext: function () {
|
||||||
|
let findmntOnce = memoizee(async () => {
|
||||||
|
return treeMapAsync(await findmnt(), async (mount) => {
|
||||||
|
return {
|
||||||
|
... mount,
|
||||||
|
sourceDevice: (mount.sourceDevice?.startsWith("/"))
|
||||||
|
? await fs.realpath(mount.sourceDevice)
|
||||||
|
: mount.sourceDevice
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
findmnt: new DataLoader(async function (mountpoints) {
|
||||||
|
let mounts = await findmntOnce();
|
||||||
|
|
||||||
|
// TODO: It's kind of strange that it sometimes returns a tree and sometimes a list, this can probably be improved?
|
||||||
|
let matches = mountpoints.map((mountpoint) => {
|
||||||
|
if (mountpoint === All) {
|
||||||
|
return mounts;
|
||||||
|
} else {
|
||||||
|
return treeFind(mounts, (mount) => mount.mountpoint === mountpoint);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return matches;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
},
|
||||||
|
types: {
|
||||||
|
"sysquery.mounts.Mount": function ({ mountpoint }) {
|
||||||
|
return dlayerSource.withSources({
|
||||||
|
mountpoint: mountpoint,
|
||||||
|
$sources: {
|
||||||
|
findmnt: {
|
||||||
|
[dlayerSource.ID]: mountpoint,
|
||||||
|
id: "id",
|
||||||
|
// FIXME: Aren't we inferring the below somewhere else in the code, using the square brackets?
|
||||||
|
type: (mount) => (mount.rootPath === "/")
|
||||||
|
? "ROOT_MOUNT"
|
||||||
|
: "SUBMOUNT",
|
||||||
|
filesystem: "filesystem",
|
||||||
|
options: "options",
|
||||||
|
label: "label",
|
||||||
|
uuid: "uuid",
|
||||||
|
partitionLabel: "partitionLabel",
|
||||||
|
partitionUUID: "partitionUUID",
|
||||||
|
deviceNumber: "deviceNumber",
|
||||||
|
sourceDevicePath: "sourceDevice",
|
||||||
|
totalSpace: "totalSpace",
|
||||||
|
freeSpace: "freeSpace",
|
||||||
|
usedSpace: "usedSpace",
|
||||||
|
rootPath: "rootPath",
|
||||||
|
taskID: "taskID",
|
||||||
|
optionalFields: "optionalFields",
|
||||||
|
propagationFlags: "propagationFlags",
|
||||||
|
children: (mount, { $make }) => mount.children.map((child) => {
|
||||||
|
return $make("sysquery.mounts.Mount", { mountpoint: child.mountpoint });
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extensions: {
|
||||||
|
"sysquery.blockDevices.BlockDevice": {
|
||||||
|
// TODO: Eventually make this produce a (filtered) tree instead?
|
||||||
|
mounts: async function ({ type }, { findmnt, $make, $getProperty, $getPropertyPath }) {
|
||||||
|
let mountTree = await findmnt.load(All);
|
||||||
|
|
||||||
|
let relevantMounts = await treeFilterFlatAsync(mountTree, async (mount) => {
|
||||||
|
let mountObject = $make("sysquery.mounts.Mount", { mountpoint: mount.mountpoint });
|
||||||
|
// console.log({ sourceDevice: await $getProperty(mountObject, "sourceDevice") });
|
||||||
|
let sourcePath = await $getPropertyPath(mountObject, "sourceDevice.path");
|
||||||
|
let sourceName = await $getPropertyPath(mountObject, "sourceDevice.name");
|
||||||
|
|
||||||
|
// TODO: This logic looks strange. Is it actually correct, even when only one of name/path is specified upon BlockDevice construction?
|
||||||
|
let matchesDevice = (
|
||||||
|
(sourcePath != null && sourcePath === await $getProperty(this, "path"))
|
||||||
|
|| (sourceName != null && sourceName === await $getProperty(this, "name"))
|
||||||
|
);
|
||||||
|
|
||||||
|
let matchesType = (
|
||||||
|
type == null
|
||||||
|
|| await $getProperty(mountObject, "type" === type)
|
||||||
|
);
|
||||||
|
|
||||||
|
return matchesDevice && matchesType;
|
||||||
|
}, { recurseFilteredSubtrees: true });
|
||||||
|
|
||||||
|
// This is a bit hacky; this approach should probably be replaced by a map-filter instead. But for now, this will do - as this all happens in the same request context, there's no real penalty to re-creating the mount objects a second time.
|
||||||
|
return relevantMounts.map((mount) => {
|
||||||
|
return $make("sysquery.mounts.Mount", { mountpoint: mount.mountpoint });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
root: {} // FIXME: Expose root mounts endpoint
|
||||||
|
};
|
Loading…
Reference in New Issue