mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Merge pull request #1582 from Dokploy/fix/allow-false-values-in-env
Fix/allow false values in env
This commit is contained in:
@@ -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);
|
||||||
|
});
|
||||||
|
|||||||
@@ -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", () => {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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}`;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
11
pnpm-lock.yaml
generated
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user