Merge pull request #1460 from Dokploy/canary

🚀 Release v0.20.2
This commit is contained in:
Mauricio Siu
2025-03-11 00:51:50 -06:00
committed by GitHub
6 changed files with 79 additions and 21 deletions

View File

@@ -166,7 +166,38 @@ describe("processTemplate", () => {
if (!baseUrl || !secretKey) return; if (!baseUrl || !secretKey) return;
expect(baseUrl).toContain(mockSchema.projectName); expect(baseUrl).toContain(mockSchema.projectName);
expect(secretKey.split("=")[1]).toHaveLength(64); const base64Value = secretKey.split("=")[1];
expect(base64Value).toBeDefined();
if (!base64Value) return;
expect(base64Value).toMatch(/^[A-Za-z0-9+/]+={0,2}$/);
expect(base64Value.length).toBeGreaterThanOrEqual(86);
expect(base64Value.length).toBeLessThanOrEqual(88);
});
it("should process env vars when provided as an array", () => {
const template: CompleteTemplate = {
metadata: {} as any,
variables: {},
config: {
domains: [],
env: [
'CLOUDFLARE_TUNNEL_TOKEN="<INSERT TOKEN>"',
'ANOTHER_VAR="some value"',
"DOMAIN=${domain}",
],
mounts: [],
},
};
const result = processTemplate(template, mockSchema);
expect(result.envs).toHaveLength(3);
// Should preserve exact format for static values
expect(result.envs[0]).toBe('CLOUDFLARE_TUNNEL_TOKEN="<INSERT TOKEN>"');
expect(result.envs[1]).toBe('ANOTHER_VAR="some value"');
// Should process variables in array items
expect(result.envs[2]).toContain(mockSchema.projectName);
}); });
it("should allow using utility functions directly in env vars", () => { it("should allow using utility functions directly in env vars", () => {
@@ -195,7 +226,12 @@ describe("processTemplate", () => {
if (!randomDomainEnv || !secretKeyEnv) return; if (!randomDomainEnv || !secretKeyEnv) return;
expect(randomDomainEnv).toContain(mockSchema.projectName); expect(randomDomainEnv).toContain(mockSchema.projectName);
expect(secretKeyEnv.split("=")[1]).toHaveLength(32); const base64Value = secretKeyEnv.split("=")[1];
expect(base64Value).toBeDefined();
if (!base64Value) return;
expect(base64Value).toMatch(/^[A-Za-z0-9+/]+={0,2}$/);
expect(base64Value.length).toBeGreaterThanOrEqual(42);
expect(base64Value.length).toBeLessThanOrEqual(44);
}); });
}); });
@@ -325,8 +361,22 @@ describe("processTemplate", () => {
if (!baseUrl || !secretKey || !totpKey) return; if (!baseUrl || !secretKey || !totpKey) return;
expect(baseUrl).toContain(mockSchema.projectName); expect(baseUrl).toContain(mockSchema.projectName);
expect(secretKey.split("=")[1]).toHaveLength(64);
expect(totpKey.split("=")[1]).toHaveLength(32); // Check base64 lengths and format
const secretKeyValue = secretKey.split("=")[1];
const totpKeyValue = totpKey.split("=")[1];
expect(secretKeyValue).toBeDefined();
expect(totpKeyValue).toBeDefined();
if (!secretKeyValue || !totpKeyValue) return;
expect(secretKeyValue).toMatch(/^[A-Za-z0-9+/]+={0,2}$/);
expect(secretKeyValue.length).toBeGreaterThanOrEqual(86);
expect(secretKeyValue.length).toBeLessThanOrEqual(88);
expect(totpKeyValue).toMatch(/^[A-Za-z0-9+/]+={0,2}$/);
expect(totpKeyValue.length).toBeGreaterThanOrEqual(42);
expect(totpKeyValue.length).toBeLessThanOrEqual(44);
// Check mounts // Check mounts
expect(result.mounts).toHaveLength(1); expect(result.mounts).toHaveLength(1);
@@ -334,8 +384,8 @@ describe("processTemplate", () => {
expect(mount).toBeDefined(); expect(mount).toBeDefined();
if (!mount) return; if (!mount) return;
expect(mount.content).toContain(mockSchema.projectName); expect(mount.content).toContain(mockSchema.projectName);
expect(mount.content).toMatch(/secret=[A-Za-z0-9+/]{64}/); expect(mount.content).toMatch(/secret=[A-Za-z0-9+/]{86,88}/);
expect(mount.content).toMatch(/totp=[A-Za-z0-9+/]{32}/); expect(mount.content).toMatch(/totp=[A-Za-z0-9+/]{42,44}/);
}); });
}); });

View File

@@ -186,7 +186,7 @@ export const ShowProjects = () => {
target="_blank" target="_blank"
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`} href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
> >
<span>{domain.host}</span> <span className="truncate">{domain.host}</span>
<ExternalLinkIcon className="size-4 shrink-0" /> <ExternalLinkIcon className="size-4 shrink-0" />
</Link> </Link>
</DropdownMenuItem> </DropdownMenuItem>
@@ -222,7 +222,7 @@ export const ShowProjects = () => {
target="_blank" target="_blank"
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`} href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
> >
<span>{domain.host}</span> <span className="truncate">{domain.host}</span>
<ExternalLinkIcon className="size-4 shrink-0" /> <ExternalLinkIcon className="size-4 shrink-0" />
</Link> </Link>
</DropdownMenuItem> </DropdownMenuItem>

View File

@@ -1,6 +1,6 @@
{ {
"name": "dokploy", "name": "dokploy",
"version": "v0.20.1", "version": "v0.20.2",
"private": true, "private": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"type": "module", "type": "module",

View File

@@ -607,7 +607,7 @@ export const composeRouter = createTRPCRouter({
const processedTemplate = processTemplate(config, { const processedTemplate = processTemplate(config, {
serverIp: serverIp, serverIp: serverIp,
projectName: compose.project.name, projectName: compose.appName,
}); });
return { return {
@@ -676,7 +676,7 @@ export const composeRouter = createTRPCRouter({
const processedTemplate = processTemplate(config, { const processedTemplate = processTemplate(config, {
serverIp: serverIp, serverIp: serverIp,
projectName: compose.project.name, projectName: compose.appName,
}); });
// Update compose file // Update compose file

View File

@@ -53,14 +53,12 @@ export const generatePassword = (quantity = 16): string => {
}; };
/** /**
* Generate a random base64 string of specified length * Generate a random base64 string from N random bytes
* @param bytes Number of random bytes to generate before base64 encoding (default: 32)
* @returns base64 encoded string of the random bytes
*/ */
export function generateBase64(length: number): string { export function generateBase64(bytes = 32): string {
// To get N characters in base64, we need to generate N * 3/4 bytes return randomBytes(bytes).toString("base64");
const bytesNeeded = Math.ceil((length * 3) / 4);
return Buffer.from(randomBytes(bytesNeeded))
.toString("base64")
.substring(0, length);
} }
export function generateJwt(length = 256): string { export function generateJwt(length = 256): string {

View File

@@ -45,7 +45,7 @@ export interface CompleteTemplate {
variables: Record<string, string>; variables: Record<string, string>;
config: { config: {
domains: DomainConfig[]; domains: DomainConfig[];
env: Record<string, string>; env: Record<string, string> | string[];
mounts?: MountConfig[]; mounts?: MountConfig[];
}; };
} }
@@ -176,7 +176,6 @@ export function processDomains(
schema: Schema, schema: Schema,
): Template["domains"] { ): Template["domains"] {
if (!template?.config?.domains) return []; if (!template?.config?.domains) return [];
return template?.config?.domains?.map((domain: DomainConfig) => ({ return template?.config?.domains?.map((domain: DomainConfig) => ({
...domain, ...domain,
host: domain.host host: domain.host
@@ -195,7 +194,18 @@ export function processEnvVars(
): Template["envs"] { ): Template["envs"] {
if (!template?.config?.env) return []; if (!template?.config?.env) return [];
return Object.entries(template?.config?.env).map( // Handle array of env vars
if (Array.isArray(template.config.env)) {
return template.config.env.map((env) => {
if (typeof env === "string") {
return processValue(env, variables, schema);
}
return env;
});
}
// Handle object of env vars
return Object.entries(template.config.env).map(
([key, value]: [string, string]) => { ([key, value]: [string, string]) => {
const processedValue = processValue(value, variables, schema); const processedValue = processValue(value, variables, schema);
return `${key}=${processedValue}`; return `${key}=${processedValue}`;