refactor(deployments): improve build error

This commit is contained in:
Mauricio Siu
2024-09-15 14:48:40 -06:00
parent d2c8632c4f
commit 0d3c978aad
16 changed files with 464 additions and 178 deletions

View File

@@ -189,12 +189,14 @@ export const applicationRouter = createTRPCRouter({
redeploy: protectedProcedure redeploy: protectedProcedure
.input(apiFindOneApplication) .input(apiFindOneApplication)
.mutation(async ({ input }) => { .mutation(async ({ input }) => {
const application = await findApplicationById(input.applicationId);
const jobData: DeploymentJob = { const jobData: DeploymentJob = {
applicationId: input.applicationId, applicationId: input.applicationId,
titleLog: "Rebuild deployment", titleLog: "Rebuild deployment",
descriptionLog: "", descriptionLog: "",
type: "redeploy", type: "redeploy",
applicationType: "application", applicationType: "application",
server: !!application.serverId,
}; };
await myQueue.add( await myQueue.add(
"deployments", "deployments",
@@ -334,13 +336,14 @@ export const applicationRouter = createTRPCRouter({
deploy: protectedProcedure deploy: protectedProcedure
.input(apiFindOneApplication) .input(apiFindOneApplication)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
// const application = await findApplicationById(input.applicationId); const application = await findApplicationById(input.applicationId);
const jobData: DeploymentJob = { const jobData: DeploymentJob = {
applicationId: input.applicationId, applicationId: input.applicationId,
titleLog: "Manual deployment", titleLog: "Manual deployment",
descriptionLog: "", descriptionLog: "",
type: "deploy", type: "deploy",
applicationType: "application", applicationType: "application",
server: !!application.serverId,
}; };
await myQueue.add( await myQueue.add(
"deployments", "deployments",
@@ -350,10 +353,6 @@ export const applicationRouter = createTRPCRouter({
removeOnFail: true, removeOnFail: true,
}, },
); );
// if (!application.serverId) {
// } else {
// await enqueueDeploymentJob(application.serverId, jobData);
// }
}), }),
cleanQueues: protectedProcedure cleanQueues: protectedProcedure

View File

@@ -169,14 +169,6 @@ export const composeRouter = createTRPCRouter({
deploy: protectedProcedure deploy: protectedProcedure
.input(apiFindCompose) .input(apiFindCompose)
.mutation(async ({ input }) => { .mutation(async ({ input }) => {
// const jobData: DeploymentJob = {
// composeId: input.composeId,
// titleLog: "Manual deployment",
// type: "deploy",
// applicationType: "compose",
// descriptionLog: "",
// };
const compose = await findComposeById(input.composeId); const compose = await findComposeById(input.composeId);
const jobData: DeploymentJob = { const jobData: DeploymentJob = {
composeId: input.composeId, composeId: input.composeId,
@@ -184,6 +176,7 @@ export const composeRouter = createTRPCRouter({
type: "deploy", type: "deploy",
applicationType: "compose", applicationType: "compose",
descriptionLog: "", descriptionLog: "",
server: !!compose.serverId,
}; };
await myQueue.add( await myQueue.add(
"deployments", "deployments",
@@ -193,10 +186,6 @@ export const composeRouter = createTRPCRouter({
removeOnFail: true, removeOnFail: true,
}, },
); );
// if (!compose.serverId) {
// } else {
// await enqueueDeploymentJob(compose.serverId, jobData);
// }
}), }),
redeploy: protectedProcedure redeploy: protectedProcedure
.input(apiFindCompose) .input(apiFindCompose)

View File

@@ -260,10 +260,66 @@ export const rebuildApplication = async ({
description: descriptionLog, description: descriptionLog,
}); });
try {
if (application.sourceType === "github") {
await buildApplication(application, deployment.logPath);
} else if (application.sourceType === "gitlab") {
await buildApplication(application, deployment.logPath);
} else if (application.sourceType === "bitbucket") {
await buildApplication(application, deployment.logPath);
} else if (application.sourceType === "docker") {
await buildDocker(application, deployment.logPath);
} else if (application.sourceType === "git") {
await buildApplication(application, deployment.logPath);
} else if (application.sourceType === "drop") {
await buildApplication(application, deployment.logPath);
}
await updateDeploymentStatus(deployment.deploymentId, "done");
await updateApplicationStatus(applicationId, "done");
} catch (error) {
await updateDeploymentStatus(deployment.deploymentId, "error");
await updateApplicationStatus(applicationId, "error");
throw error;
}
return true;
};
export const deployRemoteApplication = async ({
applicationId,
titleLog = "Manual deployment",
descriptionLog = "",
}: {
applicationId: string;
titleLog: string;
descriptionLog: string;
}) => {
const application = await findApplicationById(applicationId);
const buildLink = `${await getDokployUrl()}/dashboard/project/${application.projectId}/services/application/${application.applicationId}?tab=deployments`;
const deployment = await createDeployment({
applicationId: applicationId,
title: titleLog,
description: descriptionLog,
});
try { try {
if (application.serverId) { if (application.serverId) {
let command = "set -e;"; let command = "set -e;";
if (application.sourceType === "docker") { if (application.sourceType === "github") {
command += await getGithubCloneCommand(application, deployment.logPath);
} else if (application.sourceType === "gitlab") {
command += await getGitlabCloneCommand(application, deployment.logPath);
} else if (application.sourceType === "bitbucket") {
command += await getBitbucketCloneCommand(
application,
deployment.logPath,
);
} else if (application.sourceType === "git") {
command += await getCustomGitCloneCommand(
application,
deployment.logPath,
);
} else if (application.sourceType === "docker") {
command += await buildRemoteDocker(application, deployment.logPath); command += await buildRemoteDocker(application, deployment.logPath);
} }
@@ -272,20 +328,67 @@ export const rebuildApplication = async ({
} }
await execAsyncRemote(application.serverId, command); await execAsyncRemote(application.serverId, command);
await mechanizeDockerContainer(application); await mechanizeDockerContainer(application);
} else { }
if (application.sourceType === "github") {
await buildApplication(application, deployment.logPath); await updateDeploymentStatus(deployment.deploymentId, "done");
} else if (application.sourceType === "gitlab") { await updateApplicationStatus(applicationId, "done");
await buildApplication(application, deployment.logPath);
} else if (application.sourceType === "bitbucket") { await sendBuildSuccessNotifications({
await buildApplication(application, deployment.logPath); projectName: application.project.name,
} else if (application.sourceType === "docker") { applicationName: application.name,
await buildDocker(application, deployment.logPath); applicationType: "application",
} else if (application.sourceType === "git") { buildLink,
await buildApplication(application, deployment.logPath); });
} else if (application.sourceType === "drop") { } catch (error) {
await buildApplication(application, deployment.logPath); await updateDeploymentStatus(deployment.deploymentId, "error");
await updateApplicationStatus(applicationId, "error");
await sendBuildErrorNotifications({
projectName: application.project.name,
applicationName: application.name,
applicationType: "application",
// @ts-ignore
errorMessage: error?.message || "Error to build",
buildLink,
});
console.log(
"Error on ",
application.buildType,
"/",
application.sourceType,
error,
);
throw error;
}
return true;
};
export const rebuildRemoteApplication = async ({
applicationId,
titleLog = "Rebuild deployment",
descriptionLog = "",
}: {
applicationId: string;
titleLog: string;
descriptionLog: string;
}) => {
const application = await findApplicationById(applicationId);
const deployment = await createDeployment({
applicationId: applicationId,
title: titleLog,
description: descriptionLog,
});
try {
if (application.serverId) {
if (application.sourceType !== "docker") {
let command = "set -e;";
command += getBuildCommand(application, deployment.logPath);
await execAsyncRemote(application.serverId, command);
} }
await mechanizeDockerContainer(application);
} }
await updateDeploymentStatus(deployment.deploymentId, "done"); await updateDeploymentStatus(deployment.deploymentId, "done");
await updateApplicationStatus(applicationId, "done"); await updateApplicationStatus(applicationId, "done");

View File

@@ -210,64 +210,18 @@ export const deployCompose = async ({
}); });
try { try {
if (compose.serverId) { if (compose.sourceType === "github") {
let command = ` await cloneGithubRepository(compose, deployment.logPath, true);
set -e; } else if (compose.sourceType === "gitlab") {
`; await cloneGitlabRepository(compose, deployment.logPath, true);
if (compose.sourceType === "github") { } else if (compose.sourceType === "bitbucket") {
command += await getGithubCloneCommand( await cloneBitbucketRepository(compose, deployment.logPath, true);
compose, } else if (compose.sourceType === "git") {
deployment.logPath, await cloneGitRepository(compose, deployment.logPath, true);
true, } else if (compose.sourceType === "raw") {
); await createComposeFile(compose, deployment.logPath);
} else if (compose.sourceType === "gitlab") {
command += await getGitlabCloneCommand(
compose,
deployment.logPath,
true,
);
} else if (compose.sourceType === "bitbucket") {
command += await getBitbucketCloneCommand(
compose,
deployment.logPath,
true,
);
} else if (compose.sourceType === "git") {
command += await getCustomGitCloneCommand(
compose,
deployment.logPath,
true,
);
} else if (compose.sourceType === "raw") {
command += getCreateComposeFileCommand(compose);
}
async function* sequentialSteps() {
yield execAsyncRemote(compose.serverId, command);
yield getBuildComposeCommand(compose, deployment.logPath);
}
const steps = sequentialSteps();
for await (const step of steps) {
step;
}
console.log(" ---- done ----");
} else {
if (compose.sourceType === "github") {
await cloneGithubRepository(compose, deployment.logPath, true);
} else if (compose.sourceType === "gitlab") {
await cloneGitlabRepository(compose, deployment.logPath, true);
} else if (compose.sourceType === "bitbucket") {
await cloneBitbucketRepository(compose, deployment.logPath, true);
} else if (compose.sourceType === "git") {
await cloneGitRepository(compose, deployment.logPath, true);
} else if (compose.sourceType === "raw") {
await createComposeFile(compose, deployment.logPath);
}
await buildCompose(compose, deployment.logPath);
} }
await buildCompose(compose, deployment.logPath);
await updateDeploymentStatus(deployment.deploymentId, "done"); await updateDeploymentStatus(deployment.deploymentId, "done");
await updateCompose(composeId, { await updateCompose(composeId, {
composeStatus: "done", composeStatus: "done",
@@ -334,6 +288,132 @@ export const rebuildCompose = async ({
return true; return true;
}; };
export const deployRemoteCompose = async ({
composeId,
titleLog = "Manual deployment",
descriptionLog = "",
}: {
composeId: string;
titleLog: string;
descriptionLog: string;
}) => {
const compose = await findComposeById(composeId);
const buildLink = `${await getDokployUrl()}/dashboard/project/${compose.projectId}/services/compose/${compose.composeId}?tab=deployments`;
const deployment = await createDeploymentCompose({
composeId: composeId,
title: titleLog,
description: descriptionLog,
});
try {
if (compose.serverId) {
let command = "set -e;";
if (compose.sourceType === "github") {
command += await getGithubCloneCommand(
compose,
deployment.logPath,
true,
);
} else if (compose.sourceType === "gitlab") {
command += await getGitlabCloneCommand(
compose,
deployment.logPath,
true,
);
} else if (compose.sourceType === "bitbucket") {
command += await getBitbucketCloneCommand(
compose,
deployment.logPath,
true,
);
} else if (compose.sourceType === "git") {
command += await getCustomGitCloneCommand(
compose,
deployment.logPath,
true,
);
} else if (compose.sourceType === "raw") {
command += getCreateComposeFileCommand(compose, deployment.logPath);
}
async function* sequentialSteps() {
yield execAsyncRemote(compose.serverId, command);
yield getBuildComposeCommand(compose, deployment.logPath);
}
const steps = sequentialSteps();
for await (const step of steps) {
step;
}
console.log(" ---- done ----");
}
await updateDeploymentStatus(deployment.deploymentId, "done");
await updateCompose(composeId, {
composeStatus: "done",
});
await sendBuildSuccessNotifications({
projectName: compose.project.name,
applicationName: compose.name,
applicationType: "compose",
buildLink,
});
} catch (error) {
await updateDeploymentStatus(deployment.deploymentId, "error");
await updateCompose(composeId, {
composeStatus: "error",
});
await sendBuildErrorNotifications({
projectName: compose.project.name,
applicationName: compose.name,
applicationType: "compose",
// @ts-ignore
errorMessage: error?.message || "Error to build",
buildLink,
});
throw error;
}
};
export const rebuildRemoteCompose = async ({
composeId,
titleLog = "Rebuild deployment",
descriptionLog = "",
}: {
composeId: string;
titleLog: string;
descriptionLog: string;
}) => {
const compose = await findComposeById(composeId);
const deployment = await createDeploymentCompose({
composeId: composeId,
title: titleLog,
description: descriptionLog,
});
try {
if (compose.serverId) {
await getBuildComposeCommand(compose, deployment.logPath);
}
await updateDeploymentStatus(deployment.deploymentId, "done");
await updateCompose(composeId, {
composeStatus: "done",
});
} catch (error) {
await updateDeploymentStatus(deployment.deploymentId, "error");
await updateCompose(composeId, {
composeStatus: "error",
});
throw error;
}
return true;
};
export const removeCompose = async (compose: Compose) => { export const removeCompose = async (compose: Compose) => {
try { try {
const projectPath = join(COMPOSE_PATH, compose.appName); const projectPath = join(COMPOSE_PATH, compose.appName);

View File

@@ -1,12 +1,16 @@
import { type Job, Worker } from "bullmq"; import { type Job, Worker } from "bullmq";
import { import {
deployApplication, deployApplication,
deployRemoteApplication,
rebuildApplication, rebuildApplication,
rebuildRemoteApplication,
updateApplicationStatus, updateApplicationStatus,
} from "../api/services/application"; } from "../api/services/application";
import { import {
deployCompose, deployCompose,
deployRemoteCompose,
rebuildCompose, rebuildCompose,
rebuildRemoteCompose,
updateCompose, updateCompose,
} from "../api/services/compose"; } from "../api/services/compose";
import { myQueue, redisConfig } from "./queueSetup"; import { myQueue, redisConfig } from "./queueSetup";
@@ -16,6 +20,7 @@ type DeployJob =
applicationId: string; applicationId: string;
titleLog: string; titleLog: string;
descriptionLog: string; descriptionLog: string;
server?: boolean;
type: "deploy" | "redeploy"; type: "deploy" | "redeploy";
applicationType: "application"; applicationType: "application";
} }
@@ -23,6 +28,7 @@ type DeployJob =
composeId: string; composeId: string;
titleLog: string; titleLog: string;
descriptionLog: string; descriptionLog: string;
server?: boolean;
type: "deploy" | "redeploy"; type: "deploy" | "redeploy";
applicationType: "compose"; applicationType: "compose";
}; };
@@ -35,35 +41,68 @@ export const deploymentWorker = new Worker(
try { try {
if (job.data.applicationType === "application") { if (job.data.applicationType === "application") {
await updateApplicationStatus(job.data.applicationId, "running"); await updateApplicationStatus(job.data.applicationId, "running");
if (job.data.type === "redeploy") { if (job.data.server) {
await rebuildApplication({ if (job.data.type === "redeploy") {
applicationId: job.data.applicationId, await rebuildRemoteApplication({
titleLog: job.data.titleLog, applicationId: job.data.applicationId,
descriptionLog: job.data.descriptionLog, titleLog: job.data.titleLog,
}); descriptionLog: job.data.descriptionLog,
} else if (job.data.type === "deploy") { });
await deployApplication({ } else if (job.data.type === "deploy") {
applicationId: job.data.applicationId, await deployRemoteApplication({
titleLog: job.data.titleLog, applicationId: job.data.applicationId,
descriptionLog: job.data.descriptionLog, titleLog: job.data.titleLog,
}); descriptionLog: job.data.descriptionLog,
});
}
} else {
if (job.data.type === "redeploy") {
await rebuildApplication({
applicationId: job.data.applicationId,
titleLog: job.data.titleLog,
descriptionLog: job.data.descriptionLog,
});
} else if (job.data.type === "deploy") {
await deployApplication({
applicationId: job.data.applicationId,
titleLog: job.data.titleLog,
descriptionLog: job.data.descriptionLog,
});
}
} }
} else if (job.data.applicationType === "compose") { } else if (job.data.applicationType === "compose") {
await updateCompose(job.data.composeId, { await updateCompose(job.data.composeId, {
composeStatus: "running", composeStatus: "running",
}); });
if (job.data.type === "deploy") {
await deployCompose({ if (job.data.server) {
composeId: job.data.composeId, if (job.data.type === "redeploy") {
titleLog: job.data.titleLog, await rebuildRemoteCompose({
descriptionLog: job.data.descriptionLog, composeId: job.data.composeId,
}); titleLog: job.data.titleLog,
} else if (job.data.type === "redeploy") { descriptionLog: job.data.descriptionLog,
await rebuildCompose({ });
composeId: job.data.composeId, } else if (job.data.type === "deploy") {
titleLog: job.data.titleLog, await deployRemoteCompose({
descriptionLog: job.data.descriptionLog, composeId: job.data.composeId,
}); titleLog: job.data.titleLog,
descriptionLog: job.data.descriptionLog,
});
}
} else {
if (job.data.type === "deploy") {
await deployCompose({
composeId: job.data.composeId,
titleLog: job.data.titleLog,
descriptionLog: job.data.descriptionLog,
});
} else if (job.data.type === "redeploy") {
await rebuildCompose({
composeId: job.data.composeId,
titleLog: job.data.titleLog,
descriptionLog: job.data.descriptionLog,
});
}
} }
} }
} catch (error) { } catch (error) {

View File

@@ -9,6 +9,7 @@ import { COMPOSE_PATH } from "@/server/constants";
import type { InferResultType } from "@/server/types/with"; import type { InferResultType } from "@/server/types/with";
import boxen from "boxen"; import boxen from "boxen";
import { import {
getComposePath,
writeDomainsToCompose, writeDomainsToCompose,
writeDomainsToComposeRemote, writeDomainsToComposeRemote,
} from "../docker/domain"; } from "../docker/domain";
@@ -73,12 +74,17 @@ export const getBuildComposeCommand = async (
compose: ComposeNested, compose: ComposeNested,
logPath: string, logPath: string,
) => { ) => {
const { sourceType, appName, mounts, composeType, domains } = compose; const { sourceType, appName, mounts, composeType, domains, composePath } =
compose;
const command = createCommand(compose); const command = createCommand(compose);
const envCommand = getCreateEnvFileCommand(compose); const envCommand = getCreateEnvFileCommand(compose);
const projectPath = join(COMPOSE_PATH, compose.appName, "code"); const projectPath = join(COMPOSE_PATH, compose.appName, "code");
const newCompose = await writeDomainsToComposeRemote(compose, domains); const newCompose = await writeDomainsToComposeRemote(
compose,
domains,
logPath,
);
const logContent = ` const logContent = `
App Name: ${appName} App Name: ${appName}
Build Compose 🐳 Build Compose 🐳
@@ -107,6 +113,11 @@ Compose Type: ${composeType} ✅`;
${envCommand} ${envCommand}
cd "${projectPath}"; cd "${projectPath}";
if [ ! -f "${composePath}" ]; then
echo "❌ Error: Compose file not found" >> "${logPath}";
exit 1;
fi
docker ${command.split(" ").join(" ")} >> "${logPath}" 2>&1 || { echo "Error: ❌ Docker command failed" >> "${logPath}"; exit 1; } docker ${command.split(" ").join(" ")} >> "${logPath}" 2>&1 || { echo "Error: ❌ Docker command failed" >> "${logPath}"; exit 1; }

View File

@@ -6,7 +6,7 @@ import {
getDockerContextPath, getDockerContextPath,
} from "../filesystem/directory"; } from "../filesystem/directory";
import { spawnAsync } from "../process/spawnAsync"; import { spawnAsync } from "../process/spawnAsync";
import { createEnvFile } from "./utils"; import { createEnvFile, createEnvFileCommand } from "./utils";
export const buildCustomDocker = async ( export const buildCustomDocker = async (
application: ApplicationNested, application: ApplicationNested,
@@ -86,11 +86,27 @@ export const getDockerCommand = (
commandArgs.push("--build-arg", arg); commandArgs.push("--build-arg", arg);
} }
const command = ` /*
Do not generate an environment file when publishDirectory is specified,
as it could be publicly exposed.
*/
let command = "";
if (!publishDirectory) {
command += createEnvFileCommand(dockerFilePath, env);
}
command = `
echo "Building ${appName}" >> ${logPath}; echo "Building ${appName}" >> ${logPath};
cd ${dockerContextPath} || exit 1; cd ${dockerContextPath} >> ${logPath} 2>> ${logPath} || {
docker ${commandArgs.join(" ")} >> ${logPath} 2>&1; echo "❌ The path ${dockerContextPath} does not exist" >> ${logPath};
echo "Docker build completed." >> ${logPath}; exit 1;
}
docker ${commandArgs.join(" ")} >> ${logPath} 2>> ${logPath} || {
echo "❌ Docker build failed" >> ${logPath};
exit 1;
}
echo "✅ Docker build completed." >> ${logPath};
`; `;
return command; return command;

View File

@@ -62,8 +62,11 @@ export const getHerokuCommand = (
const command = `pack ${args.join(" ")}`; const command = `pack ${args.join(" ")}`;
const bashCommand = ` const bashCommand = `
echo "Starting heroku build..." >> ${logPath}; echo "Starting heroku build..." >> ${logPath};
${command} >> ${logPath} 2>&1; ${command} >> ${logPath} 2>> ${logPath} || {
echo "Heroku build completed." >> ${logPath}; echo "Heroku build failed" >> ${logPath};
exit 1;
}
echo "✅ Heroku build completed." >> ${logPath};
`; `;
return bashCommand; return bashCommand;

View File

@@ -16,7 +16,7 @@ import { buildCustomDocker, getDockerCommand } from "./docker-file";
import { buildHeroku, getHerokuCommand } from "./heroku"; import { buildHeroku, getHerokuCommand } from "./heroku";
import { buildNixpacks, getNixpacksCommand } from "./nixpacks"; import { buildNixpacks, getNixpacksCommand } from "./nixpacks";
import { buildPaketo, getPaketoCommand } from "./paketo"; import { buildPaketo, getPaketoCommand } from "./paketo";
import { buildStatic } from "./static"; import { buildStatic, getStaticCommand } from "./static";
import { findServerById } from "@/server/api/services/server"; import { findServerById } from "@/server/api/services/server";
import { readSSHKey } from "../filesystem/ssh"; import { readSSHKey } from "../filesystem/ssh";
import { getRemoteDocker } from "../servers/remote-docker"; import { getRemoteDocker } from "../servers/remote-docker";
@@ -81,8 +81,8 @@ export const getBuildCommand = (
return getHerokuCommand(application, logPath); return getHerokuCommand(application, logPath);
case "paketo_buildpacks": case "paketo_buildpacks":
return getPaketoCommand(application, logPath); return getPaketoCommand(application, logPath);
// case "static": case "static":
// return buildStatic(application, writeStream); return getStaticCommand(application, logPath);
case "dockerfile": case "dockerfile":
return getDockerCommand(application, logPath); return getDockerCommand(application, logPath);
} }

View File

@@ -1,12 +1,11 @@
import type { WriteStream } from "node:fs"; import type { WriteStream } from "node:fs";
import path from "node:path"; import path from "node:path";
import { buildStatic } from "@/server/utils/builders/static"; import { buildStatic, getStaticCommand } from "@/server/utils/builders/static";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import type { ApplicationNested } from "."; import type { ApplicationNested } from ".";
import { prepareEnvironmentVariables } from "../docker/utils"; import { prepareEnvironmentVariables } from "../docker/utils";
import { getBuildAppDirectory } from "../filesystem/directory"; import { getBuildAppDirectory } from "../filesystem/directory";
import { spawnAsync } from "../process/spawnAsync"; import { spawnAsync } from "../process/spawnAsync";
import { executeCommand } from "../servers/command";
export const buildNixpacks = async ( export const buildNixpacks = async (
application: ApplicationNested, application: ApplicationNested,
@@ -83,47 +82,44 @@ export const getNixpacksCommand = (
const buildContainerId = `${appName}-${nanoid(10)}`; const buildContainerId = `${appName}-${nanoid(10)}`;
const envVariables = prepareEnvironmentVariables(env); const envVariables = prepareEnvironmentVariables(env);
try { const args = ["build", buildAppDirectory, "--name", appName];
const args = ["build", buildAppDirectory, "--name", appName];
for (const env of envVariables) { for (const env of envVariables) {
args.push("--env", env); args.push("--env", env);
} }
if (publishDirectory) { if (publishDirectory) {
/* No need for any start command, since we'll use nginx later on */ /* No need for any start command, since we'll use nginx later on */
args.push("--no-error-without-start"); args.push("--no-error-without-start");
} }
console.log("args", args); console.log("args", args);
const command = `nixpacks ${args.join(" ")}`; const command = `nixpacks ${args.join(" ")}`;
const bashCommand = ` let bashCommand = `
echo "Starting nixpacks build..." >> ${logPath}; echo "Starting nixpacks build..." >> ${logPath};
${command} >> ${logPath} 2>&1; ${command} >> ${logPath} 2>> ${logPath} || {
echo "Nixpacks build completed." >> ${logPath}; echo "Nixpacks build failed" >> ${logPath};
exit 1;
}
echo "✅ Nixpacks build completed." >> ${logPath};
`; `;
/* /*
Run the container with the image created by nixpacks, Run the container with the image created by nixpacks,
and copy the artifacts on the host filesystem. and copy the artifacts on the host filesystem.
Then, remove the container and create a static build. Then, remove the container and create a static build.
*/ */
if (publishDirectory) {
if (publishDirectory) { bashCommand += `
} docker create --name ${buildContainerId} ${appName}
docker cp ${buildContainerId}:/app/${publishDirectory} ${path.join(buildAppDirectory, publishDirectory)} >> ${logPath} 2>> ${logPath} || {
// if (publishDirectory) { docker rm ${buildContainerId}
// bashCommand += ` echo "❌ Copying ${publishDirectory} to ${path.join(buildAppDirectory, publishDirectory)} failed" >> ${logPath};
// docker create --name ${buildContainerId} ${appName} exit 1;
// docker cp ${buildContainerId}:/app/${publishDirectory} ${path.join(buildAppDirectory, publishDirectory)} }
// docker rm ${buildContainerId} docker rm ${buildContainerId}
// buildStatic ${application} ${writeStream} ${getStaticCommand(application, logPath)}
// `; `;
// }
return bashCommand;
} catch (e) {
// await spawnAsync("docker", ["rm", buildContainerId], writeToStream);
throw e;
} }
return bashCommand;
}; };

View File

@@ -61,8 +61,11 @@ export const getPaketoCommand = (
const command = `pack ${args.join(" ")}`; const command = `pack ${args.join(" ")}`;
const bashCommand = ` const bashCommand = `
echo "Starting Paketo build..." >> ${logPath}; echo "Starting Paketo build..." >> ${logPath};
${command} >> ${logPath} 2>&1; ${command} >> ${logPath} 2>> ${logPath} || {
echo "Paketo build completed." >> ${logPath}; echo "Paketo build failed" >> ${logPath};
exit 1;
}
echo "✅ Paketo build completed." >> ${logPath};
`; `;
return bashCommand; return bashCommand;

View File

@@ -1,7 +1,10 @@
import type { WriteStream } from "node:fs"; import type { WriteStream } from "node:fs";
import { buildCustomDocker } from "@/server/utils/builders/docker-file"; import {
buildCustomDocker,
getDockerCommand,
} from "@/server/utils/builders/docker-file";
import type { ApplicationNested } from "."; import type { ApplicationNested } from ".";
import { createFile } from "../docker/utils"; import { createFile, getCreateFileCommand } from "../docker/utils";
import { getBuildAppDirectory } from "../filesystem/directory"; import { getBuildAppDirectory } from "../filesystem/directory";
export const buildStatic = async ( export const buildStatic = async (
@@ -36,3 +39,31 @@ export const buildStatic = async (
throw e; throw e;
} }
}; };
export const getStaticCommand = (
application: ApplicationNested,
logPath: string,
) => {
const { publishDirectory } = application;
const buildAppDirectory = getBuildAppDirectory(application);
let command = getCreateFileCommand(
buildAppDirectory,
"Dockerfile",
[
"FROM nginx:alpine",
"WORKDIR /usr/share/nginx/html/",
`COPY ${publishDirectory || "."} .`,
].join("\n"),
);
command += getDockerCommand(
{
...application,
buildType: "dockerfile",
dockerfile: "Dockerfile",
},
logPath,
);
return command;
};

View File

@@ -13,7 +13,6 @@ export const createEnvFile = (directory: string, env: string | null) => {
export const createEnvFileCommand = (directory: string, env: string | null) => { export const createEnvFileCommand = (directory: string, env: string | null) => {
const envFilePath = join(dirname(directory), ".env"); const envFilePath = join(dirname(directory), ".env");
// let command = ``
if (!existsSync(dirname(envFilePath))) { if (!existsSync(dirname(envFilePath))) {
mkdirSync(dirname(envFilePath), { recursive: true }); mkdirSync(dirname(envFilePath), { recursive: true });
} }

View File

@@ -96,7 +96,14 @@ export const loadDockerComposeRemote = async (
if (!compose.serverId) { if (!compose.serverId) {
return null; return null;
} }
const { stdout } = await execAsyncRemote(compose.serverId, `cat ${path}`); const { stdout, stderr } = await execAsyncRemote(
compose.serverId,
`cat ${path}`,
);
if (stderr) {
return null;
}
if (!stdout) return null; if (!stdout) return null;
const parsedConfig = load(stdout) as ComposeSpecification; const parsedConfig = load(stdout) as ComposeSpecification;
return parsedConfig; return parsedConfig;
@@ -135,21 +142,25 @@ export const writeDomainsToCompose = async (
export const writeDomainsToComposeRemote = async ( export const writeDomainsToComposeRemote = async (
compose: Compose, compose: Compose,
domains: Domain[], domains: Domain[],
logPath: string,
) => { ) => {
if (!domains.length) { if (!domains.length) {
return ""; return "";
} }
const composeConverted = await addDomainToCompose(compose, domains);
const path = getComposePath(compose);
try { try {
const composeConverted = await addDomainToCompose(compose, domains);
const path = getComposePath(compose);
if (compose.serverId) { if (compose.serverId) {
const composeString = dump(composeConverted, { lineWidth: 1000 }); const composeString = dump(composeConverted, { lineWidth: 1000 });
const encodedContent = encodeBase64(composeString); const encodedContent = encodeBase64(composeString);
return `echo "${encodedContent}" | base64 -d > "${path}";`; return `echo "${encodedContent}" | base64 -d > "${path}";`;
} }
} catch (error) { } catch (error) {
throw error; return `
echo "❌ Has occured an error: ${error?.message || error}" >> ${logPath};
exit 1;
`;
} }
}; };
// (node:59875) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 SIGTERM listeners added to [process]. Use emitter.setMaxListeners() to increase limit // (node:59875) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 SIGTERM listeners added to [process]. Use emitter.setMaxListeners() to increase limit

View File

@@ -59,24 +59,26 @@ export const buildRemoteDocker = async (
throw new Error("Docker image not found"); throw new Error("Docker image not found");
} }
let command = ` let command = `
echo "Building ${sourceType}" >> ${logPath};
echo "Pulling ${dockerImage}" >> ${logPath}; echo "Pulling ${dockerImage}" >> ${logPath};
`; `;
if (username && password) { if (username && password) {
command += ` command += `
if ! docker login --username ${username} --password ${password} https://index.docker.io/v1/ >> ${logPath} 2>&1; then if ! docker login --username ${username} --password ${password} https://index.docker.io/v1/ >> ${logPath} 2>&1; then
echo "Error logging in to Docker Hub" >> ${logPath}; echo "❌ Login failed" >> ${logPath};
exit 1; exit 1;
fi fi
`; `;
} }
command += ` command += `
echo "Pulling ${dockerImage}" >> ${logPath}; docker pull ${dockerImage} >> ${logPath} 2>> ${logPath} || {
docker pull ${dockerImage} >> ${logPath} 2>&1; echo "❌ Pulling image failed" >> ${logPath};
`; exit 1;
}
echo "✅ Pulling image completed." >> ${logPath};
`;
return command; return command;
} catch (error) { } catch (error) {
throw error; throw error;

View File

@@ -29,7 +29,10 @@ export const createComposeFile = async (compose: Compose, logPath: string) => {
} }
}; };
export const getCreateComposeFileCommand = (compose: Compose) => { export const getCreateComposeFileCommand = (
compose: Compose,
logPath: string,
) => {
const { appName, composeFile } = compose; const { appName, composeFile } = compose;
const outputPath = join(COMPOSE_PATH, appName, "code"); const outputPath = join(COMPOSE_PATH, appName, "code");
const filePath = join(outputPath, "docker-compose.yml"); const filePath = join(outputPath, "docker-compose.yml");
@@ -38,6 +41,7 @@ export const getCreateComposeFileCommand = (compose: Compose) => {
rm -rf ${outputPath}; rm -rf ${outputPath};
mkdir -p ${outputPath}; mkdir -p ${outputPath};
echo "${encodedContent}" | base64 -d > "${filePath}"; echo "${encodedContent}" | base64 -d > "${filePath}";
echo "File 'docker-compose.yml' created: ✅" >> ${logPath};
`; `;
return bashCommand; return bashCommand;
}; };