Skip to content

Commit

Permalink
Add discovery search for projects within stacks directory that are not
Browse files Browse the repository at this point in the history
known to docker compose
  • Loading branch information
mkoo21 committed Dec 15, 2024
1 parent 5115032 commit 33de00a
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 3 deletions.
66 changes: 63 additions & 3 deletions backend/stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { DockgeSocket, fileExists, ValidationError } from "./util-server";
import path from "path";
import {
acceptedComposeFileNames,
acceptedComposeFileNamePattern,
ArbitrarilyNestedLooseObject,
COMBINED_TERMINAL_COLS,
COMBINED_TERMINAL_ROWS,
CREATED_FILE,
Expand Down Expand Up @@ -271,7 +273,7 @@ export class Stack {
return stackList;
}

// Get status from docker compose ls
// Get stacks from docker compose ls
let res = await childProcessAsync.spawn("docker", [ "compose", "ls", "--all", "--format", "json" ], {
encoding: "utf-8",
});
Expand All @@ -282,6 +284,7 @@ export class Stack {
}

let composeList = JSON.parse(res.stdout.toString());
let pathSearchTree: ArbitrarilyNestedLooseObject = {}; // search structure for matching paths

for (let composeStack of composeList) {
try {
Expand All @@ -292,11 +295,69 @@ export class Stack {
stack._configFilePath = path.dirname(composeFiles[0]);
stack._composeFileName = path.basename(composeFiles[0]);
stackList.set(composeStack.Name, stack);

// add project path to search structure to use later
// e.g. path "/opt/stacks" would yield the tree { opt: stacks: {} }
path.join(stack._configFilePath, stack._composeFileName).split(path.sep).reduce((searchTree, pathComponent) => {
if (pathComponent == "") {
return searchTree;
}
if (!searchTree[pathComponent]) {
searchTree[pathComponent] = {};
}
return searchTree[pathComponent];
}, pathSearchTree);
} catch (e) {
if (e instanceof Error) {
log.warn("getStackList", `Failed to get stack ${composeStack.Name}, error: ${e.message}`);
log.error("getStackList", `Failed to get stack ${composeStack.Name}, error: ${e.message}`);
}
}
}

// Search stacks directory for compose files not associated with a running compose project (ie. never started through CLI)
try {
// Hopefully the user has access to everything in this directory! If they don't, log the error. It is a small price to pay for fast searching.
let rawFilesList = fs.readdirSync(server.stacksDir, {
recursive: true,
withFileTypes: true
});
let acceptedComposeFiles = rawFilesList.filter((dirEnt: fs.Dirent) => dirEnt.isFile() && !!dirEnt.name.match(acceptedComposeFileNamePattern));
log.debug("getStackList", `Folder scan yielded ${acceptedComposeFiles.length} files`);
for (let composeFile of acceptedComposeFiles) {
// check if we have seen this file before
let fullPath = composeFile.parentPath;
let previouslySeen = fullPath.split(path.sep).reduce((searchTree: ArbitrarilyNestedLooseObject | boolean, pathComponent) => {
if (pathComponent == "") {
return searchTree;
}

// end condition
if (searchTree == false || !(searchTree as ArbitrarilyNestedLooseObject)[pathComponent]) {
return false;
}

// path (so far) has been previously seen
return (searchTree as ArbitrarilyNestedLooseObject)[pathComponent];
}, pathSearchTree);
if (!previouslySeen) {
// a file with an accepted compose filename has been found that did not appear in `docker compose ls`. Use its config file path as a temp name
log.info("getStackList", `Found project unknown to docker compose: ${fullPath}/${composeFile.name}`);
let [ configFilePath, configFilename, inferredProjectName ] = [ fullPath, composeFile.name, path.basename(fullPath) ];
if (stackList.get(inferredProjectName)) {
log.info("getStackList", `... but it was ignored. A project named ${inferredProjectName} already exists`);
} else {
let stack = new Stack(server, inferredProjectName);
stack._status = UNKNOWN;
stack._configFilePath = configFilePath;
stack._composeFileName = configFilename;
stackList.set(inferredProjectName, stack);
}
}
}
} catch (e) {
if (e instanceof Error) {
log.error("getStackList", `Got error searching for undiscovered stacks:\n${e.message}`);
}
}

this.managedStackList = stackList;
Expand Down Expand Up @@ -483,6 +544,5 @@ export class Stack {
log.error("getServiceStatusList", e);
return statusList;
}

}
}
11 changes: 11 additions & 0 deletions common/util-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export interface LooseObject {
[key: string]: any
}

export interface ArbitrarilyNestedLooseObject {
[key: string]: ArbitrarilyNestedLooseObject | Record<string, never>;
}

export interface BaseRes {
ok: boolean;
msg?: string;
Expand Down Expand Up @@ -125,6 +129,13 @@ export const acceptedComposeFileNames = [
"compose.yml",
];

// Make a regex out of accepted compose file names
export const acceptedComposeFileNamePattern = new RegExp(
acceptedComposeFileNames
.map((filename: string) => filename.replace(".", "\\$&"))
.join("|")
);

/**
* Generate a decimal integer number from a string
* @param str Input
Expand Down

0 comments on commit 33de00a

Please sign in to comment.