refactor: add path join to prevent concatenate double slash and update the getImageName

This commit is contained in:
Mauricio Siu
2024-12-19 02:05:30 -06:00
parent 844d582147
commit c51b502116
2 changed files with 250 additions and 248 deletions

View File

@@ -4,12 +4,12 @@ import type { InferResultType } from "@dokploy/server/types/with";
import type { CreateServiceOptions } from "dockerode"; import type { CreateServiceOptions } from "dockerode";
import { uploadImage, uploadImageRemoteCommand } from "../cluster/upload"; import { uploadImage, uploadImageRemoteCommand } from "../cluster/upload";
import { import {
calculateResources, calculateResources,
generateBindMounts, generateBindMounts,
generateConfigContainer, generateConfigContainer,
generateFileMounts, generateFileMounts,
generateVolumeMounts, generateVolumeMounts,
prepareEnvironmentVariables, prepareEnvironmentVariables,
} from "../docker/utils"; } from "../docker/utils";
import { getRemoteDocker } from "../servers/remote-docker"; import { getRemoteDocker } from "../servers/remote-docker";
import { buildCustomDocker, getDockerCommand } from "./docker-file"; import { buildCustomDocker, getDockerCommand } from "./docker-file";
@@ -24,217 +24,217 @@ import { nanoid } from "nanoid";
// PAKETO codeDirectory = where is the path of the code directory // PAKETO codeDirectory = where is the path of the code directory
// DOCKERFILE codeDirectory = where is the exact path of the (Dockerfile) // DOCKERFILE codeDirectory = where is the exact path of the (Dockerfile)
export type ApplicationNested = InferResultType< export type ApplicationNested = InferResultType<
"applications", "applications",
{ {
mounts: true; mounts: true;
security: true; security: true;
redirects: true; redirects: true;
ports: true; ports: true;
registry: true; registry: true;
project: true; project: true;
} }
>; >;
export const buildApplication = async ( export const buildApplication = async (
application: ApplicationNested, application: ApplicationNested,
logPath: string, logPath: string
) => { ) => {
const writeStream = createWriteStream(logPath, { flags: "a" }); const writeStream = createWriteStream(logPath, { flags: "a" });
const { buildType, sourceType } = application; const { buildType, sourceType } = application;
try { try {
writeStream.write( writeStream.write(
`\nBuild ${buildType}: ✅\nSource Type: ${sourceType}: ✅\n`, `\nBuild ${buildType}: ✅\nSource Type: ${sourceType}: ✅\n`
); );
console.log(`Build ${buildType}: ✅`); console.log(`Build ${buildType}: ✅`);
if (buildType === "nixpacks") { if (buildType === "nixpacks") {
await buildNixpacks(application, writeStream); await buildNixpacks(application, writeStream);
} else if (buildType === "heroku_buildpacks") { } else if (buildType === "heroku_buildpacks") {
await buildHeroku(application, writeStream); await buildHeroku(application, writeStream);
} else if (buildType === "paketo_buildpacks") { } else if (buildType === "paketo_buildpacks") {
await buildPaketo(application, writeStream); await buildPaketo(application, writeStream);
} else if (buildType === "dockerfile") { } else if (buildType === "dockerfile") {
await buildCustomDocker(application, writeStream); await buildCustomDocker(application, writeStream);
} else if (buildType === "static") { } else if (buildType === "static") {
await buildStatic(application, writeStream); await buildStatic(application, writeStream);
} }
if (application.registryId) { if (application.registryId) {
await uploadImage(application, writeStream); await uploadImage(application, writeStream);
} }
await mechanizeDockerContainer(application); await mechanizeDockerContainer(application);
writeStream.write("Docker Deployed: ✅"); writeStream.write("Docker Deployed: ✅");
} catch (error) { } catch (error) {
if (error instanceof Error) { if (error instanceof Error) {
writeStream.write(`Error ❌\n${error?.message}`); writeStream.write(`Error ❌\n${error?.message}`);
} else { } else {
writeStream.write("Error ❌"); writeStream.write("Error ❌");
} }
throw error; throw error;
} finally { } finally {
writeStream.end(); writeStream.end();
} }
}; };
export const getBuildCommand = ( export const getBuildCommand = (
application: ApplicationNested, application: ApplicationNested,
logPath: string, logPath: string
) => { ) => {
let command = ""; let command = "";
const { buildType, registry } = application; const { buildType, registry } = application;
switch (buildType) { switch (buildType) {
case "nixpacks": case "nixpacks":
command = getNixpacksCommand(application, logPath); command = getNixpacksCommand(application, logPath);
break; break;
case "heroku_buildpacks": case "heroku_buildpacks":
command = getHerokuCommand(application, logPath); command = getHerokuCommand(application, logPath);
break; break;
case "paketo_buildpacks": case "paketo_buildpacks":
command = getPaketoCommand(application, logPath); command = getPaketoCommand(application, logPath);
break; break;
case "static": case "static":
command = getStaticCommand(application, logPath); command = getStaticCommand(application, logPath);
break; break;
case "dockerfile": case "dockerfile":
command = getDockerCommand(application, logPath); command = getDockerCommand(application, logPath);
break; break;
} }
if (registry) { if (registry) {
command += uploadImageRemoteCommand(application, logPath); command += uploadImageRemoteCommand(application, logPath);
} }
return command; return command;
}; };
export const mechanizeDockerContainer = async ( export const mechanizeDockerContainer = async (
application: ApplicationNested, application: ApplicationNested
) => { ) => {
const { const {
appName, appName,
env, env,
mounts, mounts,
cpuLimit, cpuLimit,
memoryLimit, memoryLimit,
memoryReservation, memoryReservation,
cpuReservation, cpuReservation,
command, command,
ports, ports,
} = application; } = application;
const resources = calculateResources({ const resources = calculateResources({
memoryLimit, memoryLimit,
memoryReservation, memoryReservation,
cpuLimit, cpuLimit,
cpuReservation, cpuReservation,
}); });
const volumesMount = generateVolumeMounts(mounts); const volumesMount = generateVolumeMounts(mounts);
const { const {
HealthCheck, HealthCheck,
RestartPolicy, RestartPolicy,
Placement, Placement,
Labels, Labels,
Mode, Mode,
RollbackConfig, RollbackConfig,
UpdateConfig, UpdateConfig,
Networks, Networks,
} = generateConfigContainer(application); } = generateConfigContainer(application);
const bindsMount = generateBindMounts(mounts); const bindsMount = generateBindMounts(mounts);
const filesMount = generateFileMounts(appName, application); const filesMount = generateFileMounts(appName, application);
const envVariables = prepareEnvironmentVariables( const envVariables = prepareEnvironmentVariables(
env, env,
application.project.env, application.project.env
); );
const image = getImageName(application); const image = getImageName(application);
const authConfig = getAuthConfig(application); const authConfig = getAuthConfig(application);
const docker = await getRemoteDocker(application.serverId); const docker = await getRemoteDocker(application.serverId);
const settings: CreateServiceOptions = { const settings: CreateServiceOptions = {
authconfig: authConfig, authconfig: authConfig,
Name: appName, Name: appName,
TaskTemplate: { TaskTemplate: {
ContainerSpec: { ContainerSpec: {
HealthCheck, HealthCheck,
Image: image, Image: image,
Env: envVariables, Env: envVariables,
Mounts: [...volumesMount, ...bindsMount, ...filesMount], Mounts: [...volumesMount, ...bindsMount, ...filesMount],
...(command ...(command
? { ? {
Command: ["/bin/sh"], Command: ["/bin/sh"],
Args: ["-c", command], Args: ["-c", command],
} }
: {}), : {}),
Labels, Labels,
}, },
Networks, Networks,
RestartPolicy, RestartPolicy,
Placement, Placement,
Resources: { Resources: {
...resources, ...resources,
}, },
}, },
Mode, Mode,
RollbackConfig, RollbackConfig,
EndpointSpec: { EndpointSpec: {
Ports: ports.map((port) => ({ Ports: ports.map((port) => ({
Protocol: port.protocol, Protocol: port.protocol,
TargetPort: port.targetPort, TargetPort: port.targetPort,
PublishedPort: port.publishedPort, PublishedPort: port.publishedPort,
})), })),
}, },
UpdateConfig, UpdateConfig,
}; };
try { try {
const service = docker.getService(appName); const service = docker.getService(appName);
const inspect = await service.inspect(); const inspect = await service.inspect();
await service.update({ await service.update({
version: Number.parseInt(inspect.Version.Index), version: Number.parseInt(inspect.Version.Index),
...settings, ...settings,
TaskTemplate: { TaskTemplate: {
...settings.TaskTemplate, ...settings.TaskTemplate,
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1, ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
}, },
}); });
} catch (error) { } catch (error) {
await docker.createService(settings); await docker.createService(settings);
} }
}; };
const getImageName = (application: ApplicationNested) => { const getImageName = (application: ApplicationNested) => {
const { appName, sourceType, dockerImage, registry } = application; const { appName, sourceType, dockerImage, registry } = application;
if (sourceType === "docker") { if (sourceType === "docker") {
return dockerImage || "ERROR-NO-IMAGE-PROVIDED"; return dockerImage || "ERROR-NO-IMAGE-PROVIDED";
} }
if (registry) { if (registry) {
return join(registry.imagePrefix || "", appName); return join(registry.registryUrl, registry.imagePrefix || "", appName);
} }
return `${appName}:latest`; return `${appName}:latest`;
}; };
const getAuthConfig = (application: ApplicationNested) => { const getAuthConfig = (application: ApplicationNested) => {
const { registry, username, password, sourceType } = application; const { registry, username, password, sourceType, registryUrl } = application;
if (sourceType === "docker") { if (sourceType === "docker") {
if (username && password) { if (username && password) {
return { return {
password, password,
username, username,
serveraddress: "https://index.docker.io/v1/", serveraddress: registryUrl || "",
}; };
} }
} else if (registry) { } else if (registry) {
return { return {
password: registry.password, password: registry.password,
username: registry.username, username: registry.username,
serveraddress: registry.registryUrl, serveraddress: registry.registryUrl,
}; };
} }
return undefined; return undefined;
}; };

View File

@@ -1,81 +1,84 @@
import type { WriteStream } from "node:fs"; import type { WriteStream } from "node:fs";
import { join } from "node:path"; import path, { join } from "node:path";
import type { ApplicationNested } from "../builders"; import type { ApplicationNested } from "../builders";
import { spawnAsync } from "../process/spawnAsync"; import { spawnAsync } from "../process/spawnAsync";
export const uploadImage = async ( export const uploadImage = async (
application: ApplicationNested, application: ApplicationNested,
writeStream: WriteStream, writeStream: WriteStream
) => { ) => {
const registry = application.registry; const registry = application.registry;
if (!registry) { if (!registry) {
throw new Error("Registry not found"); throw new Error("Registry not found");
} }
const { registryUrl, imagePrefix, registryType } = registry; const { registryUrl, imagePrefix } = registry;
const { appName } = application; const { appName } = application;
const imageName = `${appName}:latest`; const imageName = `${appName}:latest`;
const finalURL = registryUrl; const finalURL = registryUrl;
const registryTag = const registryTag = path
`${registryUrl}/${join(imagePrefix || "", imageName)}`.replace(/\/+/g, "/"); .join(registryUrl, join(imagePrefix || "", imageName))
.replace(/\/+/g, "/");
try { try {
writeStream.write( writeStream.write(
`📦 [Enabled Registry] Uploading image to ${registry.registryType} | ${imageName} | ${finalURL}\n`, `📦 [Enabled Registry] Uploading image to ${registry.registryType} | ${imageName} | ${finalURL}\n`
); );
const loginCommand = spawnAsync( const loginCommand = spawnAsync(
"docker", "docker",
["login", finalURL, "-u", registry.username, "--password-stdin"], ["login", finalURL, "-u", registry.username, "--password-stdin"],
(data) => { (data) => {
if (writeStream.writable) { if (writeStream.writable) {
writeStream.write(data); writeStream.write(data);
} }
}, }
); );
loginCommand.child?.stdin?.write(registry.password); loginCommand.child?.stdin?.write(registry.password);
loginCommand.child?.stdin?.end(); loginCommand.child?.stdin?.end();
await loginCommand; await loginCommand;
await spawnAsync("docker", ["tag", imageName, registryTag], (data) => { await spawnAsync("docker", ["tag", imageName, registryTag], (data) => {
if (writeStream.writable) { if (writeStream.writable) {
writeStream.write(data); writeStream.write(data);
} }
}); });
await spawnAsync("docker", ["push", registryTag], (data) => { await spawnAsync("docker", ["push", registryTag], (data) => {
if (writeStream.writable) { if (writeStream.writable) {
writeStream.write(data); writeStream.write(data);
} }
}); });
} catch (error) { } catch (error) {
console.log(error); console.log(error);
throw error; throw error;
} }
}; };
export const uploadImageRemoteCommand = ( export const uploadImageRemoteCommand = (
application: ApplicationNested, application: ApplicationNested,
logPath: string, logPath: string
) => { ) => {
const registry = application.registry; const registry = application.registry;
if (!registry) { if (!registry) {
throw new Error("Registry not found"); throw new Error("Registry not found");
} }
const { registryUrl, imagePrefix } = registry; const { registryUrl, imagePrefix } = registry;
const { appName } = application; const { appName } = application;
const imageName = `${appName}:latest`; const imageName = `${appName}:latest`;
const finalURL = registryUrl; const finalURL = registryUrl;
const registryTag = join(imagePrefix || "", imageName); const registryTag = path
.join(registryUrl, join(imagePrefix || "", imageName))
.replace(/\/+/g, "/");
try { try {
const command = ` const command = `
echo "📦 [Enabled Registry] Uploading image to '${registry.registryType}' | '${registryTag}'" >> ${logPath}; echo "📦 [Enabled Registry] Uploading image to '${registry.registryType}' | '${registryTag}'" >> ${logPath};
echo "${registry.password}" | docker login ${finalURL} -u ${registry.username} --password-stdin >> ${logPath} 2>> ${logPath} || { echo "${registry.password}" | docker login ${finalURL} -u ${registry.username} --password-stdin >> ${logPath} 2>> ${logPath} || {
echo "❌ DockerHub Failed" >> ${logPath}; echo "❌ DockerHub Failed" >> ${logPath};
@@ -93,9 +96,8 @@ export const uploadImageRemoteCommand = (
} }
echo "✅ Image Pushed" >> ${logPath}; echo "✅ Image Pushed" >> ${logPath};
`; `;
return command; return command;
} catch (error) { } catch (error) {
console.log(error); throw error;
throw error; }
}
}; };