diff --git a/backend/agent-socket-handlers/docker-socket-handler.ts b/backend/agent-socket-handlers/docker-socket-handler.ts index 81746019..ef74630f 100644 --- a/backend/agent-socket-handlers/docker-socket-handler.ts +++ b/backend/agent-socket-handlers/docker-socket-handler.ts @@ -8,10 +8,10 @@ export class DockerSocketHandler extends AgentSocketHandler { create(socket : DockgeSocket, server : DockgeServer, agentSocket : AgentSocket) { // Do not call super.create() - agentSocket.on("deployStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown, callback) => { + agentSocket.on("deployStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, composeOverrideYAML : unknown, isAdd : unknown, callback) => { try { checkLogin(socket); - const stack = await this.saveStack(server, name, composeYAML, composeENV, isAdd); + const stack = await this.saveStack(server, name, composeYAML, composeENV, composeOverrideYAML, isAdd); await stack.deploy(socket); server.sendStackList(); callbackResult({ @@ -25,10 +25,10 @@ export class DockerSocketHandler extends AgentSocketHandler { } }); - agentSocket.on("saveStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown, callback) => { + agentSocket.on("saveStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, composeOverrideYAML : unknown, isAdd : unknown, callback) => { try { checkLogin(socket); - await this.saveStack(server, name, composeYAML, composeENV, isAdd); + await this.saveStack(server, name, composeYAML, composeENV, composeOverrideYAML, isAdd); callbackResult({ ok: true, msg: "Saved", @@ -253,7 +253,7 @@ export class DockerSocketHandler extends AgentSocketHandler { }); } - async saveStack(server : DockgeServer, name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown) : Promise { + async saveStack(server : DockgeServer, name : unknown, composeYAML : unknown, composeENV : unknown, composeOverrideYAML : unknown, isAdd : unknown) : Promise { // Check types if (typeof(name) !== "string") { throw new ValidationError("Name must be a string"); @@ -264,14 +264,16 @@ export class DockerSocketHandler extends AgentSocketHandler { if (typeof(composeENV) !== "string") { throw new ValidationError("Compose ENV must be a string"); } + if (typeof(composeOverrideYAML) !== "string") { + throw new ValidationError("Compose Override YAML must be a string"); + } if (typeof(isAdd) !== "boolean") { throw new ValidationError("isAdd must be a boolean"); } - const stack = new Stack(server, name, composeYAML, composeENV, false); + const stack = new Stack(server, name, composeYAML, composeENV, composeOverrideYAML, false); await stack.save(isAdd); return stack; } } - diff --git a/backend/stack.ts b/backend/stack.ts index fbce5002..08f89468 100644 --- a/backend/stack.ts +++ b/backend/stack.ts @@ -6,6 +6,7 @@ import { DockgeSocket, fileExists, ValidationError } from "./util-server"; import path from "path"; import { acceptedComposeFileNames, + acceptedComposeOverrideFileNames, COMBINED_TERMINAL_COLS, COMBINED_TERMINAL_ROWS, CREATED_FILE, @@ -26,19 +27,22 @@ export class Stack { protected _status: number = UNKNOWN; protected _composeYAML?: string; protected _composeENV?: string; + protected _composeOverrideYAML?: string; protected _configFilePath?: string; protected _composeFileName: string = "compose.yaml"; + protected _composeOverrideFileName: string = "compose.override.yaml"; protected server: DockgeServer; protected combinedTerminal? : Terminal; protected static managedStackList: Map = new Map(); - constructor(server : DockgeServer, name : string, composeYAML? : string, composeENV? : string, skipFSOperations = false) { + constructor(server : DockgeServer, name : string, composeYAML? : string, composeENV? : string, composeOverrideYAML? : string, skipFSOperations = false) { this.name = name; this.server = server; this._composeYAML = composeYAML; this._composeENV = composeENV; + this._composeOverrideYAML = composeOverrideYAML; if (!skipFSOperations) { // Check if compose file name is different from compose.yaml @@ -48,6 +52,14 @@ export class Stack { break; } } + + // Check if override file exists and determine its name + for (const filename of acceptedComposeOverrideFileNames) { + if (fs.existsSync(path.join(this.path, filename))) { + this._composeOverrideFileName = filename; + break; + } + } } } @@ -74,6 +86,7 @@ export class Stack { ...obj, composeYAML: this.composeYAML, composeENV: this.composeENV, + composeOverrideYAML: this.composeOverrideYAML, primaryHostname, }; } @@ -120,6 +133,11 @@ export class Stack { // Check YAML format yaml.parse(this.composeYAML); + // Check override YAML format if it exists + if (this.composeOverrideYAML && this.composeOverrideYAML.trim() !== "") { + yaml.parse(this.composeOverrideYAML); + } + let lines = this.composeENV.split("\n"); // Check if the .env is able to pass docker-compose @@ -152,6 +170,17 @@ export class Stack { return this._composeENV; } + get composeOverrideYAML() : string { + if (this._composeOverrideYAML === undefined) { + try { + this._composeOverrideYAML = fs.readFileSync(path.join(this.path, this._composeOverrideFileName), "utf-8"); + } catch (e) { + this._composeOverrideYAML = ""; + } + } + return this._composeOverrideYAML; + } + get path() : string { return path.join(this.server.stacksDir, this.name); } @@ -204,6 +233,14 @@ export class Stack { if (await fileExists(envPath) || this.composeENV.trim() !== "") { await fsAsync.writeFile(envPath, this.composeENV); } + + const overridePath = path.join(dir, this._composeOverrideFileName); + + // Write or overwrite the compose override file + // If override file is not existing and the composeOverrideYAML is empty, we don't need to write it + if (await fileExists(overridePath) || this.composeOverrideYAML.trim() !== "") { + await fsAsync.writeFile(overridePath, this.composeOverrideYAML); + } } async deploy(socket : DockgeSocket) : Promise { @@ -399,7 +436,7 @@ export class Stack { if (!skipFSOperations) { stack = new Stack(server, stackName); } else { - stack = new Stack(server, stackName, undefined, undefined, true); + stack = new Stack(server, stackName, undefined, undefined, undefined, true); } stack._status = UNKNOWN; diff --git a/common/util-common.ts b/common/util-common.ts index cf141c9f..f7c743a1 100644 --- a/common/util-common.ts +++ b/common/util-common.ts @@ -114,6 +114,13 @@ export const acceptedComposeFileNames = [ "compose.yml", ]; +export const acceptedComposeOverrideFileNames = [ + "compose.override.yaml", + "compose.override.yml", + "docker-compose.override.yaml", + "docker-compose.override.yml", +]; + /** * Generate a decimal integer number from a string * @param str Input @@ -416,4 +423,3 @@ function traverseYAML(pair : Pair, env : DotenvParseOutput) : void { pair.value.value = envsubst(pair.value.value, env); } } - diff --git a/frontend/src/pages/Compose.vue b/frontend/src/pages/Compose.vue index 7572a23a..9b9ef6a6 100644 --- a/frontend/src/pages/Compose.vue +++ b/frontend/src/pages/Compose.vue @@ -163,6 +163,23 @@
+ +
+

compose.override.yaml

+
+ +
+
+

{{ stack.composeFileName }}

@@ -303,7 +320,7 @@ export default { combinedTerminalRows: COMBINED_TERMINAL_ROWS, combinedTerminalCols: COMBINED_TERMINAL_COLS, stack: { - + composeOverrideYAML: "", }, serviceStatusList: {}, isEditMode: false, @@ -417,6 +434,16 @@ export default { deep: true, }, + "stack.composeOverrideYAML": { + handler() { + if (this.editorFocus) { + console.debug("override yaml code changed"); + this.yamlCodeChange(); + } + }, + deep: true, + }, + jsonConfig: { handler() { if (!this.editorFocus) { @@ -580,7 +607,7 @@ export default { this.bindTerminal(); - this.$root.emitAgent(this.stack.endpoint, "deployStack", this.stack.name, this.stack.composeYAML, this.stack.composeENV, this.isAdd, (res) => { + this.$root.emitAgent(this.stack.endpoint, "deployStack", this.stack.name, this.stack.composeYAML, this.stack.composeENV, this.stack.composeOverrideYAML || "", this.isAdd, (res) => { this.processing = false; this.$root.toastRes(res); @@ -594,7 +621,7 @@ export default { saveStack() { this.processing = true; - this.$root.emitAgent(this.stack.endpoint, "saveStack", this.stack.name, this.stack.composeYAML, this.stack.composeENV, this.isAdd, (res) => { + this.$root.emitAgent(this.stack.endpoint, "saveStack", this.stack.name, this.stack.composeYAML, this.stack.composeENV, this.stack.composeOverrideYAML || "", this.isAdd, (res) => { this.processing = false; this.$root.toastRes(res);