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 1f594d5
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 2 deletions.
64 changes: 62 additions & 2 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 @@ -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 tree so we can quickly decide if we have seen it before 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 1f594d5

Please sign in to comment.