mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-26 02:06:49 +00:00
fix: stop compose-ps spam after deleting a stack (#1831)
* fix: stop compose-ps spam after deleting a stack A live logs WebSocket kept calling docker compose ps against the removed stack directory every 2s. Missing cwd surfaces as spawn ENOENT, which was logged as Docker CLI unavailable. Skip compose when the dir is gone and idle the log stream instead of retrying. * fix: contain stack directory stats inside the compose root Resolve the stack path against the node's compose directory and refuse paths that escape it before calling fs.stat, matching the existing filesystem containment pattern at other sinks.
This commit is contained in:
@@ -2480,6 +2480,25 @@ class DockerController {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* True when the stack directory is missing (ENOENT) or is not a directory.
|
||||
* Other stat failures return false so callers keep the existing compose-ps path.
|
||||
*/
|
||||
private async isStackDirectoryAbsent(stackName: string): Promise<boolean> {
|
||||
// Canonical js/path-injection barrier at the fs.stat sink.
|
||||
const baseResolved = path.resolve(NodeRegistry.getInstance().getComposeDir(this.nodeId));
|
||||
const stackDir = path.resolve(baseResolved, stackName);
|
||||
if (!stackDir.startsWith(baseResolved + path.sep)) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
const st = await fs.stat(stackDir);
|
||||
return !st.isDirectory();
|
||||
} catch (error) {
|
||||
return (error as NodeJS.ErrnoException).code === 'ENOENT';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Containers visible to `docker compose ps` for this stack. Empty when Compose
|
||||
* does not manage any containers (including first deploy or mislabeled legacy).
|
||||
@@ -2530,6 +2549,7 @@ class DockerController {
|
||||
*/
|
||||
public async getLegacyOrphanContainersByStack(stackName: string): Promise<Array<{ Id: string }>> {
|
||||
const stackDir = path.join(NodeRegistry.getInstance().getComposeDir(this.nodeId), stackName);
|
||||
if (await this.isStackDirectoryAbsent(stackName)) return [];
|
||||
const toIds = (list: Array<{ Id?: string }>) =>
|
||||
list.filter((c): c is { Id: string } => typeof c.Id === 'string' && c.Id.length > 0)
|
||||
.map((c) => ({ Id: c.Id }));
|
||||
@@ -2573,6 +2593,9 @@ class DockerController {
|
||||
| { status: 'classification_failed'; error: string }
|
||||
> {
|
||||
const stackDir = path.join(NodeRegistry.getInstance().getComposeDir(this.nodeId), stackName);
|
||||
if (await this.isStackDirectoryAbsent(stackName)) {
|
||||
return { status: 'classification_failed', error: 'Stack directory is gone' };
|
||||
}
|
||||
const toIds = (list: Array<{ Id?: string }>) =>
|
||||
list.filter((c): c is { Id: string } => typeof c.Id === 'string' && c.Id.length > 0)
|
||||
.map((c) => c.Id);
|
||||
@@ -2614,6 +2637,9 @@ class DockerController {
|
||||
// not the process default, so a non-default local node sees its own stack dir
|
||||
// and deploy spec.
|
||||
const stackDir = path.join(NodeRegistry.getInstance().getComposeDir(this.nodeId), stackName);
|
||||
if (await this.isStackDirectoryAbsent(stackName)) {
|
||||
return this.enrichContainers([]);
|
||||
}
|
||||
|
||||
try {
|
||||
const containers = await this.fetchComposePsContainers(stackName, stackDir);
|
||||
|
||||
Reference in New Issue
Block a user