Merge pull request #1582 from Dokploy/fix/allow-false-values-in-env

Fix/allow false values in env
This commit is contained in:
Mauricio Siu
2025-03-30 01:40:06 -06:00
committed by GitHub
9 changed files with 155 additions and 21 deletions

View File

@@ -1006,7 +1006,7 @@ services:
volumes: volumes:
db-config-testhash: db-config-testhash:
`) as ComposeSpecification; `);
test("Expect to change the suffix in all the possible places (4 Try)", () => { test("Expect to change the suffix in all the possible places (4 Try)", () => {
const composeData = load(composeFileComplex) as ComposeSpecification; 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); 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);
});

View File

@@ -233,6 +233,49 @@ describe("processTemplate", () => {
expect(base64Value.length).toBeGreaterThanOrEqual(42); expect(base64Value.length).toBeGreaterThanOrEqual(42);
expect(base64Value.length).toBeLessThanOrEqual(44); 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", () => { describe("mounts processing", () => {

View File

@@ -150,7 +150,8 @@
"ws": "8.16.0", "ws": "8.16.0",
"xterm-addon-fit": "^0.8.0", "xterm-addon-fit": "^0.8.0",
"zod": "^3.23.4", "zod": "^3.23.4",
"zod-form-data": "^2.0.2" "zod-form-data": "^2.0.2",
"toml": "3.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/adm-zip": "^0.5.5", "@types/adm-zip": "^0.5.5",

View File

@@ -50,7 +50,8 @@ import {
import { processTemplate } from "@dokploy/server/templates/processors"; import { processTemplate } from "@dokploy/server/templates/processors";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { dump, load } from "js-yaml"; import { dump } from "js-yaml";
import { parse } from "toml";
import _ from "lodash"; import _ from "lodash";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { z } from "zod"; import { z } from "zod";
@@ -594,7 +595,7 @@ export const composeRouter = createTRPCRouter({
serverIp = "127.0.0.1"; serverIp = "127.0.0.1";
} }
const templateData = JSON.parse(decodedData); const templateData = JSON.parse(decodedData);
const config = load(templateData.config) as CompleteTemplate; const config = parse(templateData.config) as CompleteTemplate;
if (!templateData.compose || !config) { if (!templateData.compose || !config) {
throw new TRPCError({ throw new TRPCError({
@@ -663,7 +664,8 @@ export const composeRouter = createTRPCRouter({
} }
const templateData = JSON.parse(decodedData); const templateData = JSON.parse(decodedData);
const config = load(templateData.config) as CompleteTemplate;
const config = parse(templateData.config) as CompleteTemplate;
if (!templateData.compose || !config) { if (!templateData.compose || !config) {
throw new TRPCError({ throw new TRPCError({
@@ -678,7 +680,6 @@ export const composeRouter = createTRPCRouter({
projectName: compose.appName, projectName: compose.appName,
}); });
// Update compose file
await updateCompose(input.composeId, { await updateCompose(input.composeId, {
composeFile: templateData.compose, composeFile: templateData.compose,
sourceType: "raw", sourceType: "raw",
@@ -686,7 +687,6 @@ export const composeRouter = createTRPCRouter({
isolatedDeployment: true, isolatedDeployment: true,
}); });
// Create mounts
if (processedTemplate.mounts && processedTemplate.mounts.length > 0) { if (processedTemplate.mounts && processedTemplate.mounts.length > 0) {
for (const mount of processedTemplate.mounts) { for (const mount of processedTemplate.mounts) {
await createMount({ await createMount({
@@ -700,7 +700,6 @@ export const composeRouter = createTRPCRouter({
} }
} }
// Create domains
if (processedTemplate.domains && processedTemplate.domains.length > 0) { if (processedTemplate.domains && processedTemplate.domains.length > 0) {
for (const domain of processedTemplate.domains) { for (const domain of processedTemplate.domains) {
await createDomain({ await createDomain({

View File

@@ -76,7 +76,8 @@
"ws": "8.16.0", "ws": "8.16.0",
"zod": "^3.23.4", "zod": "^3.23.4",
"ssh2": "1.15.0", "ssh2": "1.15.0",
"@octokit/rest": "^20.0.2" "@octokit/rest": "^20.0.2",
"toml": "3.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/micromatch": "4.0.9", "@types/micromatch": "4.0.9",

View File

@@ -1,4 +1,4 @@
import { load } from "js-yaml"; import { parse } from "toml";
/** /**
* Complete template interface that includes both metadata and configuration * Complete template interface that includes both metadata and configuration
@@ -86,7 +86,7 @@ export async function fetchTemplateFiles(
try { try {
// Fetch both files in parallel // Fetch both files in parallel
const [templateYmlResponse, dockerComposeResponse] = await Promise.all([ 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`), fetch(`${baseUrl}/blueprints/${templateId}/docker-compose.yml`),
]); ]);
@@ -99,7 +99,7 @@ export async function fetchTemplateFiles(
dockerComposeResponse.text(), dockerComposeResponse.text(),
]); ]);
const config = load(templateYml) as CompleteTemplate; const config = parse(templateYml) as CompleteTemplate;
return { config, dockerCompose }; return { config, dockerCompose };
} catch (error) { } catch (error) {

View File

@@ -45,7 +45,9 @@ export interface CompleteTemplate {
variables: Record<string, string>; variables: Record<string, string>;
config: { config: {
domains: DomainConfig[]; domains: DomainConfig[];
env: Record<string, string> | string[]; env:
| Record<string, string | boolean | number>
| (string | Record<string, string | boolean | number>)[];
mounts?: MountConfig[]; mounts?: MountConfig[];
}; };
} }
@@ -200,17 +202,27 @@ export function processEnvVars(
if (typeof env === "string") { if (typeof env === "string") {
return processValue(env, variables, schema); 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);
}); });
} }
// Handle object of env vars // Handle object of env vars
return Object.entries(template.config.env).map( return Object.entries(template.config.env).map(([key, value]) => {
([key, value]: [string, string]) => { if (typeof value === "string") {
const processedValue = processValue(value, variables, schema); const processedValue = processValue(value, variables, schema);
return `${key}=${processedValue}`; return `${key}=${processedValue}`;
}, }
); return `${key}=${value}`;
});
} }
/** /**

View File

@@ -30,12 +30,22 @@ export const addSuffixToVolumesInServices = (
// skip bind mounts and variables (e.g. $PWD) // skip bind mounts and variables (e.g. $PWD)
if ( if (
volumeName?.startsWith(".") || !volumeName ||
volumeName?.startsWith("/") || volumeName.startsWith(".") ||
volumeName?.startsWith("$") volumeName.startsWith("/") ||
volumeName.startsWith("$")
) { ) {
return volume; 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}`; return `${volumeName}-${suffix}:${path}`;
} }
if (_.isObject(volume) && volume.type === "volume" && volume.source) { if (_.isObject(volume) && volume.type === "volume" && volume.source) {

11
pnpm-lock.yaml generated
View File

@@ -424,6 +424,9 @@ importers:
tailwindcss-animate: tailwindcss-animate:
specifier: ^1.0.7 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))) 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: undici:
specifier: ^6.19.2 specifier: ^6.19.2
version: 6.19.4 version: 6.19.4
@@ -723,6 +726,9 @@ importers:
ssh2: ssh2:
specifier: 1.15.0 specifier: 1.15.0
version: 1.15.0 version: 1.15.0
toml:
specifier: 3.0.0
version: 3.0.0
ws: ws:
specifier: 8.16.0 specifier: 8.16.0
version: 8.16.0 version: 8.16.0
@@ -6982,6 +6988,9 @@ packages:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'} engines: {node: '>=0.6'}
toml@3.0.0:
resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==}
tr46@0.0.3: tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
@@ -14061,6 +14070,8 @@ snapshots:
toidentifier@1.0.1: {} toidentifier@1.0.1: {}
toml@3.0.0: {}
tr46@0.0.3: {} tr46@0.0.3: {}
tree-dump@1.0.2(tslib@2.6.3): tree-dump@1.0.2(tslib@2.6.3):