refactor: add .env docker stack

This commit is contained in:
Mauricio Siu
2025-01-31 01:20:10 -06:00
parent f7a29accb1
commit 009859faa9
3 changed files with 571 additions and 589 deletions

View File

@@ -211,12 +211,12 @@ const Service = (
<TabsList <TabsList
className={cn( className={cn(
"md:grid md:w-fit max-md:overflow-y-scroll justify-start", "md:grid md:w-fit max-md:overflow-y-scroll justify-start",
data?.serverId ? "md:grid-cols-6" : "md:grid-cols-7", data?.serverId ? "md:grid-cols-7" : "md:grid-cols-7",
data?.composeType === "docker-compose" data?.composeType === "docker-compose"
? "" ? ""
: "md:grid-cols-6", : "md:grid-cols-7",
data?.serverId && data?.composeType === "stack" data?.serverId && data?.composeType === "stack"
? "md:grid-cols-5" ? "md:grid-cols-6"
: "", : "",
)} )}
> >

View File

@@ -2,6 +2,7 @@ import {
createWriteStream, createWriteStream,
existsSync, existsSync,
mkdirSync, mkdirSync,
readFileSync,
writeFileSync, writeFileSync,
} from "node:fs"; } from "node:fs";
import { dirname, join } from "node:path"; import { dirname, join } from "node:path";
@@ -12,7 +13,11 @@ import {
writeDomainsToCompose, writeDomainsToCompose,
writeDomainsToComposeRemote, writeDomainsToComposeRemote,
} from "../docker/domain"; } from "../docker/domain";
import { encodeBase64, prepareEnvironmentVariables } from "../docker/utils"; import {
encodeBase64,
getEnviromentVariablesObject,
prepareEnvironmentVariables,
} from "../docker/utils";
import { execAsync, execAsyncRemote } from "../process/execAsync"; import { execAsync, execAsyncRemote } from "../process/execAsync";
import { spawnAsync } from "../process/spawnAsync"; import { spawnAsync } from "../process/spawnAsync";
@@ -28,15 +33,14 @@ export const buildCompose = async (compose: ComposeNested, logPath: string) => {
const command = createCommand(compose); const command = createCommand(compose);
await writeDomainsToCompose(compose, domains); await writeDomainsToCompose(compose, domains);
createEnvFile(compose); createEnvFile(compose);
await processComposeFile(compose);
const logContent = ` const logContent = `
App Name: ${appName} App Name: ${appName}
Build Compose 🐳 Build Compose 🐳
Detected: ${mounts.length} mounts 📂 Detected: ${mounts.length} mounts 📂
Command: docker ${command} Command: docker ${command}
Source Type: docker ${sourceType} Source Type: docker ${sourceType}
Compose Type: ${composeType}`; Compose Type: ${composeType}`;
const logBox = boxen(logContent, { const logBox = boxen(logContent, {
padding: { padding: {
left: 1, left: 1,
@@ -47,7 +51,6 @@ Compose Type: ${composeType} ✅`;
borderStyle: "double", borderStyle: "double",
}); });
writeStream.write(`\n${logBox}\n`); writeStream.write(`\n${logBox}\n`);
const projectPath = join(COMPOSE_PATH, compose.appName, "code"); const projectPath = join(COMPOSE_PATH, compose.appName, "code");
await spawnAsync( await spawnAsync(
@@ -63,8 +66,11 @@ Compose Type: ${composeType} ✅`;
env: { env: {
NODE_ENV: process.env.NODE_ENV, NODE_ENV: process.env.NODE_ENV,
PATH: process.env.PATH, PATH: process.env.PATH,
...(composeType === "stack" && {
...getEnviromentVariablesObject(compose.env, compose.project.env),
}),
}, },
}, }
); );
writeStream.write("Docker Compose Deployed: ✅"); writeStream.write("Docker Compose Deployed: ✅");
@@ -78,7 +84,7 @@ Compose Type: ${composeType} ✅`;
export const getBuildComposeCommand = async ( export const getBuildComposeCommand = async (
compose: ComposeNested, compose: ComposeNested,
logPath: string, logPath: string
) => { ) => {
const { COMPOSE_PATH } = paths(true); const { COMPOSE_PATH } = paths(true);
const { sourceType, appName, mounts, composeType, domains, composePath } = const { sourceType, appName, mounts, composeType, domains, composePath } =
@@ -86,12 +92,12 @@ export const getBuildComposeCommand = async (
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 processComposeFileCommand = getProcessComposeFileCommand(compose); const exportEnvCommand = getExportEnvCommand(compose);
const newCompose = await writeDomainsToComposeRemote( const newCompose = await writeDomainsToComposeRemote(
compose, compose,
domains, domains,
logPath, logPath
); );
const logContent = ` const logContent = `
App Name: ${appName} App Name: ${appName}
@@ -122,7 +128,7 @@ Compose Type: ${composeType} ✅`;
cd "${projectPath}"; cd "${projectPath}";
${processComposeFileCommand} ${exportEnvCommand}
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; }
@@ -154,20 +160,14 @@ export const createCommand = (compose: ComposeNested) => {
} }
const path = const path =
sourceType === "raw" sourceType === "raw" ? "docker-compose.yml" : compose.composePath;
? composeType === "stack" let command = "";
? "docker-compose.processed.yml"
: "docker-compose.yml"
: composeType === "stack"
? join(dirname(compose.composePath), "docker-compose.processed.yml")
: compose.composePath;
const baseCommand = if (composeType === "stack") {
composeType === "docker-compose" command = `stack deploy -c ${path} ${appName} --prune`;
? `compose -p ${appName} -f ${path} up -d --build --remove-orphans` }
: `stack deploy -c ${path} ${appName} --prune`;
const customCommand = sanitizeCommand(compose.command); return command;
return customCommand ? `${baseCommand} ${customCommand}` : baseCommand;
}; };
const createEnvFile = (compose: ComposeNested) => { const createEnvFile = (compose: ComposeNested) => {
@@ -189,7 +189,7 @@ const createEnvFile = (compose: ComposeNested) => {
const envFileContent = prepareEnvironmentVariables( const envFileContent = prepareEnvironmentVariables(
envContent, envContent,
compose.project.env, compose.project.env
).join("\n"); ).join("\n");
if (!existsSync(dirname(envFilePath))) { if (!existsSync(dirname(envFilePath))) {
@@ -198,38 +198,6 @@ const createEnvFile = (compose: ComposeNested) => {
writeFileSync(envFilePath, envFileContent); writeFileSync(envFilePath, envFileContent);
}; };
export const processComposeFile = async (compose: ComposeNested) => {
const { COMPOSE_PATH } = paths();
if (compose.composeType === "stack") {
const command = getProcessComposeFileCommand(compose);
await execAsync(command, {
cwd: join(COMPOSE_PATH, compose.appName, "code"),
});
}
};
export const getProcessComposeFileCommand = (compose: ComposeNested) => {
const { composeType, sourceType } = compose;
const composePath =
sourceType === "raw" ? "docker-compose.yml" : compose.composePath;
const destinationPath =
sourceType === "raw"
? "docker-compose.processed.yml"
: join(dirname(compose.composePath), "docker-compose.processed.yml");
let command = "";
if (composeType === "stack") {
command = [
"export $(grep -v '^#' .env | xargs)",
`docker stack config -c ${composePath} > ${destinationPath}`,
].join(" && ");
}
return command;
};
export const getCreateEnvFileCommand = (compose: ComposeNested) => { export const getCreateEnvFileCommand = (compose: ComposeNested) => {
const { COMPOSE_PATH } = paths(true); const { COMPOSE_PATH } = paths(true);
const { env, composePath, appName } = compose; const { env, composePath, appName } = compose;
@@ -250,7 +218,7 @@ export const getCreateEnvFileCommand = (compose: ComposeNested) => {
const envFileContent = prepareEnvironmentVariables( const envFileContent = prepareEnvironmentVariables(
envContent, envContent,
compose.project.env, compose.project.env
).join("\n"); ).join("\n");
const encodedContent = encodeBase64(envFileContent); const encodedContent = encodeBase64(envFileContent);
@@ -259,3 +227,14 @@ touch ${envFilePath};
echo "${encodedContent}" | base64 -d > "${envFilePath}"; echo "${encodedContent}" | base64 -d > "${envFilePath}";
`; `;
}; };
const getExportEnvCommand = (compose: ComposeNested) => {
if (compose.composeType !== "stack") return "";
const envVars = getEnviromentVariablesObject(compose.env, compose.project.env);
const exports = Object.entries(envVars)
.map(([key, value]) => `export ${key}=${JSON.stringify(value)}`)
.join("\n");
return exports ? `\n# Export environment variables\n${exports}\n` : "";
};

View File

@@ -23,7 +23,7 @@ interface RegistryAuth {
export const pullImage = async ( export const pullImage = async (
dockerImage: string, dockerImage: string,
onData?: (data: any) => void, onData?: (data: any) => void,
authConfig?: Partial<RegistryAuth>, authConfig?: Partial<RegistryAuth>
): Promise<void> => { ): Promise<void> => {
try { try {
if (!dockerImage) { if (!dockerImage) {
@@ -41,7 +41,7 @@ export const pullImage = async (
"-p", "-p",
authConfig.password, authConfig.password,
], ],
onData, onData
); );
} }
await spawnAsync("docker", ["pull", dockerImage], onData); await spawnAsync("docker", ["pull", dockerImage], onData);
@@ -54,7 +54,7 @@ export const pullRemoteImage = async (
dockerImage: string, dockerImage: string,
serverId: string, serverId: string,
onData?: (data: any) => void, onData?: (data: any) => void,
authConfig?: Partial<RegistryAuth>, authConfig?: Partial<RegistryAuth>
): Promise<void> => { ): Promise<void> => {
try { try {
if (!dockerImage) { if (!dockerImage) {
@@ -85,9 +85,9 @@ export const pullRemoteImage = async (
}, },
(event) => { (event) => {
onData?.(event); onData?.(event);
}, }
); );
}, }
); );
}); });
} catch (error) { } catch (error) {
@@ -188,7 +188,7 @@ export const cleanUpInactiveContainers = async () => {
try { try {
const containers = await docker.listContainers({ all: true }); const containers = await docker.listContainers({ all: true });
const inactiveContainers = containers.filter( const inactiveContainers = containers.filter(
(container) => container.State !== "running", (container) => container.State !== "running"
); );
for (const container of inactiveContainers) { for (const container of inactiveContainers) {
@@ -240,7 +240,7 @@ export const startServiceRemote = async (serverId: string, appName: string) => {
export const removeService = async ( export const removeService = async (
appName: string, appName: string,
serverId?: string | null, serverId?: string | null,
deleteVolumes = false, deleteVolumes = false
) => { ) => {
try { try {
const command = `docker service rm ${appName}`; const command = `docker service rm ${appName}`;
@@ -257,7 +257,7 @@ export const removeService = async (
export const prepareEnvironmentVariables = ( export const prepareEnvironmentVariables = (
serviceEnv: string | null, serviceEnv: string | null,
projectEnv?: string | null, projectEnv?: string | null
) => { ) => {
const projectVars = parse(projectEnv ?? ""); const projectVars = parse(projectEnv ?? "");
const serviceVars = parse(serviceEnv ?? ""); const serviceVars = parse(serviceEnv ?? "");
@@ -278,12 +278,15 @@ export const prepareEnvironmentVariables = (
return resolvedVars; return resolvedVars;
}; };
export const prepareBuildArgs = (input: string | null) => { export const getEnviromentVariablesObject = (
const pairs = (input ?? "").split("\n"); input: string | null,
projectEnv?: string | null
) => {
const envs = prepareEnvironmentVariables(input, projectEnv);
const jsonObject: Record<string, string> = {}; const jsonObject: Record<string, string> = {};
for (const pair of pairs) { for (const pair of envs) {
const [key, value] = pair.split("="); const [key, value] = pair.split("=");
if (key && value) { if (key && value) {
jsonObject[key] = value; jsonObject[key] = value;
@@ -427,7 +430,7 @@ export const generateFileMounts = (
| MariadbNested | MariadbNested
| MysqlNested | MysqlNested
| PostgresNested | PostgresNested
| RedisNested, | RedisNested
) => { ) => {
const { mounts } = service; const { mounts } = service;
const { APPLICATIONS_PATH } = paths(!!service.serverId); const { APPLICATIONS_PATH } = paths(!!service.serverId);
@@ -453,7 +456,7 @@ export const generateFileMounts = (
export const createFile = async ( export const createFile = async (
outputPath: string, outputPath: string,
filePath: string, filePath: string,
content: string, content: string
) => { ) => {
try { try {
const fullPath = path.join(outputPath, filePath); const fullPath = path.join(outputPath, filePath);
@@ -475,7 +478,7 @@ export const encodeBase64 = (content: string) =>
export const getCreateFileCommand = ( export const getCreateFileCommand = (
outputPath: string, outputPath: string,
filePath: string, filePath: string,
content: string, content: string
) => { ) => {
const fullPath = path.join(outputPath, filePath); const fullPath = path.join(outputPath, filePath);
if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) { if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) {
@@ -515,7 +518,7 @@ export const getServiceContainer = async (appName: string) => {
export const getRemoteServiceContainer = async ( export const getRemoteServiceContainer = async (
serverId: string, serverId: string,
appName: string, appName: string
) => { ) => {
try { try {
const filter = { const filter = {