From e3527f7d69076c31a3f6ce928b58228527a3d589 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Wed, 26 Mar 2025 01:48:50 -0600 Subject: [PATCH 1/4] fix(processors): ensure environment variable processing handles non-string values correctly --- packages/server/src/templates/processors.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/server/src/templates/processors.ts b/packages/server/src/templates/processors.ts index 4cf48f1d8..f0f068aa4 100644 --- a/packages/server/src/templates/processors.ts +++ b/packages/server/src/templates/processors.ts @@ -205,12 +205,13 @@ export function processEnvVars( } // Handle object of env vars - return Object.entries(template.config.env).map( - ([key, value]: [string, string]) => { + return Object.entries(template.config.env).map(([key, value]) => { + if (typeof value === "string") { const processedValue = processValue(value, variables, schema); return `${key}=${processedValue}`; - }, - ); + } + return `${key}=${value}`; + }); } /** From adee87b6da00399349f7c031d0b84c733d4313f9 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 30 Mar 2025 00:32:09 -0600 Subject: [PATCH 2/4] Enhance volume handling in Docker Compose: update addSuffixToVolumesInServices to correctly manage volume paths with subdirectories and improve test coverage for suffix changes in volume names. --- .../__test__/compose/volume/volume-2.test.ts | 59 ++++++++++++++++++- .../server/src/utils/docker/compose/volume.ts | 16 ++++- 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/apps/dokploy/__test__/compose/volume/volume-2.test.ts b/apps/dokploy/__test__/compose/volume/volume-2.test.ts index bf34ed494..61cba82d3 100644 --- a/apps/dokploy/__test__/compose/volume/volume-2.test.ts +++ b/apps/dokploy/__test__/compose/volume/volume-2.test.ts @@ -1006,7 +1006,7 @@ services: volumes: db-config-testhash: -`) as ComposeSpecification; +`); test("Expect to change the suffix in all the possible places (4 Try)", () => { const composeData = load(composeFileComplex) as ComposeSpecification; @@ -1115,3 +1115,60 @@ test("Expect to change the suffix in all the possible places (5 Try)", () => { expect(updatedComposeData).toEqual(expectedDockerComposeExample1); }); + +const composeFileBackrest = ` +services: + backrest: + image: garethgeorge/backrest:v1.7.3 + restart: unless-stopped + ports: + - 9898 + environment: + - BACKREST_PORT=9898 + - BACKREST_DATA=/data + - BACKREST_CONFIG=/config/config.json + - XDG_CACHE_HOME=/cache + - TZ=\${TZ} + volumes: + - backrest/data:/data + - backrest/config:/config + - backrest/cache:/cache + - /:/userdata:ro + +volumes: + backrest: + backrest-cache: +`; + +const expectedDockerComposeBackrest = load(` +services: + backrest: + image: garethgeorge/backrest:v1.7.3 + restart: unless-stopped + ports: + - 9898 + environment: + - BACKREST_PORT=9898 + - BACKREST_DATA=/data + - BACKREST_CONFIG=/config/config.json + - XDG_CACHE_HOME=/cache + - TZ=\${TZ} + volumes: + - backrest-testhash/data:/data + - backrest-testhash/config:/config + - backrest-testhash/cache:/cache + - /:/userdata:ro + +volumes: + backrest-testhash: + backrest-cache-testhash: +`) as ComposeSpecification; + +test("Should handle volume paths with subdirectories correctly", () => { + const composeData = load(composeFileBackrest) as ComposeSpecification; + const suffix = "testhash"; + + const updatedComposeData = addSuffixToAllVolumes(composeData, suffix); + + expect(updatedComposeData).toEqual(expectedDockerComposeBackrest); +}); diff --git a/packages/server/src/utils/docker/compose/volume.ts b/packages/server/src/utils/docker/compose/volume.ts index 91bde78cb..be4c7b206 100644 --- a/packages/server/src/utils/docker/compose/volume.ts +++ b/packages/server/src/utils/docker/compose/volume.ts @@ -30,12 +30,22 @@ export const addSuffixToVolumesInServices = ( // skip bind mounts and variables (e.g. $PWD) if ( - volumeName?.startsWith(".") || - volumeName?.startsWith("/") || - volumeName?.startsWith("$") + !volumeName || + volumeName.startsWith(".") || + volumeName.startsWith("/") || + volumeName.startsWith("$") ) { return volume; } + + // Handle volume paths with subdirectories + const parts = volumeName.split("/"); + if (parts.length > 1) { + const baseName = parts[0]; + const rest = parts.slice(1).join("/"); + return `${baseName}-${suffix}/${rest}:${path}`; + } + return `${volumeName}-${suffix}:${path}`; } if (_.isObject(volume) && volume.type === "volume" && volume.source) { From 4eaf8fee0f82cda51cf7abde5b2bd92dacde256b Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 30 Mar 2025 01:12:27 -0600 Subject: [PATCH 3/4] Add toml package and update configuration parsing in Dokploy - Added toml package version 3.0.0 to package.json files in both apps/dokploy and packages/server. - Replaced js-yaml load function with toml parse function for configuration parsing in compose.ts and github.ts files, ensuring compatibility with TOML format. - Updated pnpm-lock.yaml to reflect the new toml dependency. --- apps/dokploy/package.json | 3 ++- apps/dokploy/server/api/routers/compose.ts | 11 +++++------ packages/server/package.json | 3 ++- packages/server/src/templates/github.ts | 6 +++--- pnpm-lock.yaml | 11 +++++++++++ 5 files changed, 23 insertions(+), 11 deletions(-) diff --git a/apps/dokploy/package.json b/apps/dokploy/package.json index fd813f978..bd3dc0445 100644 --- a/apps/dokploy/package.json +++ b/apps/dokploy/package.json @@ -150,7 +150,8 @@ "ws": "8.16.0", "xterm-addon-fit": "^0.8.0", "zod": "^3.23.4", - "zod-form-data": "^2.0.2" + "zod-form-data": "^2.0.2", + "toml": "3.0.0" }, "devDependencies": { "@types/adm-zip": "^0.5.5", diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index 9c4658234..c4f9b3177 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -50,7 +50,8 @@ import { import { processTemplate } from "@dokploy/server/templates/processors"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; -import { dump, load } from "js-yaml"; +import { dump } from "js-yaml"; +import { parse } from "toml"; import _ from "lodash"; import { nanoid } from "nanoid"; import { z } from "zod"; @@ -594,7 +595,7 @@ export const composeRouter = createTRPCRouter({ serverIp = "127.0.0.1"; } const templateData = JSON.parse(decodedData); - const config = load(templateData.config) as CompleteTemplate; + const config = parse(templateData.config) as CompleteTemplate; if (!templateData.compose || !config) { throw new TRPCError({ @@ -663,7 +664,8 @@ export const composeRouter = createTRPCRouter({ } const templateData = JSON.parse(decodedData); - const config = load(templateData.config) as CompleteTemplate; + + const config = parse(templateData.config) as CompleteTemplate; if (!templateData.compose || !config) { throw new TRPCError({ @@ -678,7 +680,6 @@ export const composeRouter = createTRPCRouter({ projectName: compose.appName, }); - // Update compose file await updateCompose(input.composeId, { composeFile: templateData.compose, sourceType: "raw", @@ -686,7 +687,6 @@ export const composeRouter = createTRPCRouter({ isolatedDeployment: true, }); - // Create mounts if (processedTemplate.mounts && processedTemplate.mounts.length > 0) { for (const mount of processedTemplate.mounts) { await createMount({ @@ -700,7 +700,6 @@ export const composeRouter = createTRPCRouter({ } } - // Create domains if (processedTemplate.domains && processedTemplate.domains.length > 0) { for (const domain of processedTemplate.domains) { await createDomain({ diff --git a/packages/server/package.json b/packages/server/package.json index f62157c77..7e637cb51 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -76,7 +76,8 @@ "ws": "8.16.0", "zod": "^3.23.4", "ssh2": "1.15.0", - "@octokit/rest": "^20.0.2" + "@octokit/rest": "^20.0.2", + "toml": "3.0.0" }, "devDependencies": { "@types/micromatch": "4.0.9", diff --git a/packages/server/src/templates/github.ts b/packages/server/src/templates/github.ts index bdb785ed7..a935b2155 100644 --- a/packages/server/src/templates/github.ts +++ b/packages/server/src/templates/github.ts @@ -1,4 +1,4 @@ -import { load } from "js-yaml"; +import { parse } from "toml"; /** * Complete template interface that includes both metadata and configuration @@ -86,7 +86,7 @@ export async function fetchTemplateFiles( try { // Fetch both files in parallel const [templateYmlResponse, dockerComposeResponse] = await Promise.all([ - fetch(`${baseUrl}/blueprints/${templateId}/template.yml`), + fetch(`${baseUrl}/blueprints/${templateId}/template.toml`), fetch(`${baseUrl}/blueprints/${templateId}/docker-compose.yml`), ]); @@ -99,7 +99,7 @@ export async function fetchTemplateFiles( dockerComposeResponse.text(), ]); - const config = load(templateYml) as CompleteTemplate; + const config = parse(templateYml) as CompleteTemplate; return { config, dockerCompose }; } catch (error) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac9e28093..282994f8a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -424,6 +424,9 @@ importers: tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@18.19.42)(typescript@5.5.3))) + toml: + specifier: 3.0.0 + version: 3.0.0 undici: specifier: ^6.19.2 version: 6.19.4 @@ -723,6 +726,9 @@ importers: ssh2: specifier: 1.15.0 version: 1.15.0 + toml: + specifier: 3.0.0 + version: 3.0.0 ws: specifier: 8.16.0 version: 8.16.0 @@ -6982,6 +6988,9 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + toml@3.0.0: + resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -14061,6 +14070,8 @@ snapshots: toidentifier@1.0.1: {} + toml@3.0.0: {} + tr46@0.0.3: {} tree-dump@1.0.2(tslib@2.6.3): From 84f56274714e31451c34ac4d60db7bf480941d92 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 30 Mar 2025 01:29:23 -0600 Subject: [PATCH 4/4] Enhance environment variable handling in processTemplate: support boolean and number types in env configuration, with tests for both array and object formats. --- .../templates/config.template.test.ts | 43 +++++++++++++++++++ packages/server/src/templates/processors.ts | 15 ++++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/apps/dokploy/__test__/templates/config.template.test.ts b/apps/dokploy/__test__/templates/config.template.test.ts index 9516e9d1f..d6e87cb7a 100644 --- a/apps/dokploy/__test__/templates/config.template.test.ts +++ b/apps/dokploy/__test__/templates/config.template.test.ts @@ -233,6 +233,49 @@ describe("processTemplate", () => { expect(base64Value.length).toBeGreaterThanOrEqual(42); expect(base64Value.length).toBeLessThanOrEqual(44); }); + + it("should handle boolean values in env vars when provided as an array", () => { + const template: CompleteTemplate = { + metadata: {} as any, + variables: {}, + config: { + domains: [], + env: [ + "ENABLE_USER_SIGN_UP=false", + "DEBUG_MODE=true", + "SOME_NUMBER=42", + ], + mounts: [], + }, + }; + + const result = processTemplate(template, mockSchema); + expect(result.envs).toHaveLength(3); + expect(result.envs).toContain("ENABLE_USER_SIGN_UP=false"); + expect(result.envs).toContain("DEBUG_MODE=true"); + expect(result.envs).toContain("SOME_NUMBER=42"); + }); + + it("should handle boolean values in env vars when provided as an object", () => { + const template: CompleteTemplate = { + metadata: {} as any, + variables: {}, + config: { + domains: [], + env: { + ENABLE_USER_SIGN_UP: false, + DEBUG_MODE: true, + SOME_NUMBER: 42, + }, + }, + }; + + const result = processTemplate(template, mockSchema); + expect(result.envs).toHaveLength(3); + expect(result.envs).toContain("ENABLE_USER_SIGN_UP=false"); + expect(result.envs).toContain("DEBUG_MODE=true"); + expect(result.envs).toContain("SOME_NUMBER=42"); + }); }); describe("mounts processing", () => { diff --git a/packages/server/src/templates/processors.ts b/packages/server/src/templates/processors.ts index f0f068aa4..86d3cdf74 100644 --- a/packages/server/src/templates/processors.ts +++ b/packages/server/src/templates/processors.ts @@ -45,7 +45,9 @@ export interface CompleteTemplate { variables: Record; config: { domains: DomainConfig[]; - env: Record | string[]; + env: + | Record + | (string | Record)[]; mounts?: MountConfig[]; }; } @@ -200,7 +202,16 @@ export function processEnvVars( if (typeof env === "string") { return processValue(env, variables, schema); } - return env; + // Si es un objeto, asumimos que es un par clave-valor + if (typeof env === "object" && env !== null) { + const keys = Object.keys(env); + if (keys.length > 0) { + const key = keys[0]; + return `${key}=${env[key as keyof typeof env]}`; + } + } + // Para valores primitivos (boolean, number) + return String(env); }); }