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 14, 2024
1 parent 5115032 commit 8b8f478
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 3 deletions.
60 changes: 57 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 tree for matching paths

for (let composeStack of composeList) {
try {
Expand All @@ -292,11 +295,63 @@ 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 determine if we have seen it before
// e.g. the path "/opt/stacks/project" yields the tree "{ opt: { stacks: { project: {} } } }"
path.join(stack._configFilePath, stack._composeFileName).split(path.sep).reduce((searchTree, pathComponent) => {
if (pathComponent == "") {
return searchTree;
}
if (!searchTree[pathComponent]) {
searchTree[pathComponent] = {};
}
return searchTree;
}, 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));
for (let composeFile of acceptedComposeFiles) {
// check if we have seen this file before
let fullPath = path.join(server.stacksDir, 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
let [ configFilePath, configFilename ] = [ path.dirname(fullPath), path.basename(fullPath) ];
let stack = new Stack(server, configFilePath);
stack._status = UNKNOWN;
stack._configFilePath = configFilePath;
stack._composeFileName = configFilename;
stackList.set(configFilePath, 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 +538,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 8b8f478

Please sign in to comment.