This commit is contained in:
Andras Bacsai 2022-12-07 12:17:06 +01:00
parent dcb29a80fe
commit 5e082c647c
13 changed files with 150 additions and 178 deletions

View File

@ -9,7 +9,7 @@ import autoLoad from '@fastify/autoload';
import socketIO from 'fastify-socket.io'
import socketIOServer from './realtime'
import { cleanupDockerStorage, createRemoteEngineConfiguration, decrypt, executeCommand, executeSSHCmd, generateDatabaseConfiguration, isDev, listSettings, prisma, sentryDSN, startTraefikProxy, startTraefikTCPProxy, version } from './lib/common';
import { cleanupDockerStorage, createRemoteEngineConfiguration, decrypt, executeCommand, generateDatabaseConfiguration, isDev, listSettings, prisma, sentryDSN, startTraefikProxy, startTraefikTCPProxy, version } from './lib/common';
import { scheduler } from './lib/scheduler';
import { compareVersions } from 'compare-versions';
import Graceful from '@ladjs/graceful'
@ -465,9 +465,9 @@ async function copySSLCertificates() {
async function copyRemoteCertificates(id: string, dockerId: string, remoteIpAddress: string) {
try {
await executeCommand({ command: `scp /tmp/${id}-cert.pem /tmp/${id}-key.pem ${remoteIpAddress}:/tmp/` })
await executeSSHCmd({ dockerId, command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'` })
await executeSSHCmd({ dockerId, command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/` })
await executeSSHCmd({ dockerId, command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/` })
await executeCommand({ sshCommand: true, shell: true, dockerId, command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'` })
await executeCommand({ sshCommand: true, dockerId, command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/` })
await executeCommand({ sshCommand: true, dockerId, command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/` })
} catch (error) {
console.log({ error })
}

View File

@ -159,8 +159,8 @@ import * as buildpacks from '../lib/buildPacks';
volumes: Object.assign({}, ...composeVolumes)
};
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
await executeCommand({ dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
await saveBuildLog({ line: 'Deployed successfully', buildId, applicationId });
await executeCommand({ debug: true, dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
} catch (error) {
await saveBuildLog({ line: error, buildId, applicationId });
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } })
@ -502,15 +502,13 @@ import * as buildpacks from '../lib/buildPacks';
throw new Error(`Build pack ${buildPack} not found.`);
}
} else {
if (imageFoundRemotely) {
if (imageFoundRemotely || deployNeeded) {
await saveBuildLog({ line: `Container image ${imageFound} found in Docker Registry - reuising it`, buildId, applicationId });
} else {
if (imageFoundLocally) {
if (imageFoundLocally || deployNeeded) {
await saveBuildLog({ line: `Container image ${imageFound} found locally - reuising it`, buildId, applicationId });
}
}
}
if (buildPack === 'compose') {
@ -532,8 +530,9 @@ import * as buildpacks from '../lib/buildPacks';
//
}
try {
console.log({ debug })
await executeCommand({ debug, buildId, applicationId, dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
await saveBuildLog({ line: 'Deployed successfully', buildId, applicationId });
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
await prisma.application.update({
where: { id: applicationId },
@ -633,8 +632,8 @@ import * as buildpacks from '../lib/buildPacks';
volumes: Object.assign({}, ...composeVolumes)
};
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
await executeCommand({ dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
await saveBuildLog({ line: 'Deployed successfully', buildId, applicationId });
await executeCommand({ debug, dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
} catch (error) {
await saveBuildLog({ line: error, buildId, applicationId });
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } })

View File

@ -1,4 +1,4 @@
import { base64Encode, decrypt, encrypt, executeCommand, generateTimestamp, getDomain, isARM, isDev, prisma, version } from "../common";
import { base64Encode, decrypt, encrypt, executeCommand, generateTimestamp, getDomain, isARM, isDev, prisma, version } from "../common";
import { promises as fs } from 'fs';
import { day } from "../dayjs";
@ -489,6 +489,8 @@ export const saveBuildLog = async ({
buildId: string;
applicationId: string;
}): Promise<any> => {
if (buildId === 'undefined' || buildId === 'null' || !buildId) return;
if (applicationId === 'undefined' || applicationId === 'null' || !applicationId) return;
const { default: got } = await import('got')
if (typeof line === 'object' && line) {
if (line.shortMessage) {
@ -656,17 +658,12 @@ export async function buildImage({
location = await saveDockerRegistryCredentials({ url, username, password, workdir })
}
await executeCommand({ debug, buildId, applicationId, dockerId, command: `docker ${location ? `--config ${location}` : ''} build --progress plain -f ${workdir}/${dockerFile} -t ${cache} --build-arg SOURCE_COMMIT=${commit} ${workdir}` })
await executeCommand({ stream: true, debug, buildId, applicationId, dockerId, command: `docker ${location ? `--config ${location}` : ''} build --progress plain -f ${workdir}/${dockerFile} -t ${cache} --build-arg SOURCE_COMMIT=${commit} ${workdir}` })
const { status } = await prisma.build.findUnique({ where: { id: buildId } })
if (status === 'canceled') {
throw new Error('Canceled.')
}
if (isCache) {
await saveBuildLog({ line: `Built successfully`, buildId, applicationId });
} else {
await saveBuildLog({ line: `Built successfully`, buildId, applicationId });
}
}
export function makeLabelForSimpleDockerfile({ applicationId, port, type }) {
return [

View File

@ -109,7 +109,7 @@ export default async function (data) {
dockerComposeYaml['networks'] = Object.assign({ ...networks }, { [network]: { external: true } })
await fs.writeFile(fileYaml, yaml.dump(dockerComposeYaml));
await executeCommand({ debug, buildId, applicationId, dockerId, command: `docker compose --project-directory ${workdir} pull` })
await saveBuildLog({ line: 'Pulling images from Compose file', buildId, applicationId });
await saveBuildLog({ line: 'Pulling images from Compose file...', buildId, applicationId });
await executeCommand({ debug, buildId, applicationId, dockerId, command: `docker compose --project-directory ${workdir} build --progress plain` })
await saveBuildLog({ line: 'Building images from Compose file', buildId, applicationId });
await saveBuildLog({ line: 'Building images from Compose file...', buildId, applicationId });
}

View File

@ -4,14 +4,13 @@ import { saveBuildLog } from "./common";
export default async function (data: any): Promise<void> {
const { buildId, applicationId, tag, dockerId, debug, workdir, baseDirectory, baseImage } = data
try {
await saveBuildLog({ line: `Building image started.`, buildId, applicationId });
await saveBuildLog({ line: `Building production image...`, buildId, applicationId });
await executeCommand({
buildId,
debug,
dockerId,
command: `pack build -p ${workdir}${baseDirectory} ${applicationId}:${tag} --builder ${baseImage}`
})
await saveBuildLog({ line: `Building image successful.`, buildId, applicationId });
} catch (error) {
throw error;
}

View File

@ -543,7 +543,7 @@ export async function createRemoteEngineConfiguration(id: string) {
}
return await fs.writeFile(`${homedir}/.ssh/config`, sshConfig.stringify(config));
}
export async function executeCommand({ command, dockerId = null, sshCommand = false, shell = false, buildId, applicationId, debug }: { command: string, sshCommand?: boolean, shell?: boolean, dockerId?: string, buildId?: string, applicationId?: string, debug?: boolean }): Promise<ExecaChildProcess<string>> {
export async function executeCommand({ command, dockerId = null, sshCommand = false, shell = false, stream = false, buildId, applicationId, debug }: { command: string, sshCommand?: boolean, shell?: boolean, stream?: boolean, dockerId?: string, buildId?: string, applicationId?: string, debug?: boolean }): Promise<ExecaChildProcess<string>> {
const { execa, execaCommand } = await import('execa')
const { parse } = await import('shell-quote')
const parsedCommand = parse(command);
@ -569,66 +569,78 @@ export async function executeCommand({ command, dockerId = null, sshCommand = fa
}
return await execa('ssh', [`${remoteIpAddress}-remote`, ...dockerArgs]);
}
return await new Promise(async (resolve, reject) => {
let subprocess = null;
if (stream) {
return await new Promise(async (resolve, reject) => {
let subprocess = null;
if (shell) {
subprocess = execaCommand(command, {
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine }
});
} else {
subprocess = execa(dockerCommand, dockerArgs, {
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine }
});
}
const logs = [];
subprocess.stdout.on('data', async (data) => {
const stdout = data.toString();
const array = stdout.split('\n');
for (const line of array) {
if (line !== '\n' && line !== '') {
const log = {
line: `${line.replace('\n', '')}`,
buildId,
applicationId
}
logs.push(log);
if (debug) {
await saveBuildLog(log);
}
}
}
});
subprocess.stderr.on('data', async (data) => {
const stderr = data.toString();
const array = stderr.split('\n');
for (const line of array) {
if (line !== '\n' && line !== '') {
const log = {
line: `${line.replace('\n', '')}`,
buildId,
applicationId
}
logs.push(log);
if (debug) {
await saveBuildLog(log);
}
}
}
});
subprocess.on('exit', async (code) => {
if (code === 0) {
resolve(code);
} else {
if (!debug) {
for (const log of logs) {
await saveBuildLog(log);
}
}
reject(code);
}
});
})
} else {
if (shell) {
subprocess = execaCommand(command, {
return await execaCommand(command, {
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine }
});
} else {
subprocess = execa(dockerCommand, dockerArgs, {
return await execa(dockerCommand, dockerArgs, {
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine }
});
}
const logs = [];
subprocess.stdout.on('data', async (data) => {
const stdout = data.toString();
const array = stdout.split('\n');
for (const line of array) {
if (line !== '\n' && line !== '') {
const log = {
line: `${line.replace('\n', '')}`,
buildId,
applicationId
}
logs.push(log);
if (debug) {
await saveBuildLog(log);
}
}
}
});
subprocess.stderr.on('data', async (data) => {
const stderr = data.toString();
const array = stderr.split('\n');
for (const line of array) {
if (line !== '\n' && line !== '') {
const log = {
line: `${line.replace('\n', '')}`,
buildId,
applicationId
}
logs.push(log);
if (debug) {
await saveBuildLog(log);
}
}
}
});
subprocess.on('exit', async (code) => {
await asyncSleep(1000);
if (code === 0) {
resolve(code);
} else {
if (!debug) {
for (const log of logs) {
await saveBuildLog(log);
}
}
reject(code);
}
});
})
}
} else {
if (shell) {
return execaCommand(command, { shell: true });
@ -636,44 +648,7 @@ export async function executeCommand({ command, dockerId = null, sshCommand = fa
return await execa(dockerCommand, dockerArgs);
}
}
export async function executeSSHCmd({ dockerId, command }) {
const { execaCommand, execa } = await import('execa')
const { parse } = await import('shell-quote')
let { remoteEngine, remoteIpAddress } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
if (remoteEngine) {
await createRemoteEngineConfiguration(dockerId)
}
if (process.env.CODESANDBOX_HOST) {
if (command.startsWith('docker compose')) {
command = command.replace(/docker compose/gi, 'docker-compose')
}
}
const dockerArgs = parse(command);
return await execa('ssh', [`${remoteIpAddress}-remote`, ...dockerArgs]);
}
export async function executeDockerCmd({ debug, buildId, applicationId, dockerId, command }: { debug?: boolean, buildId?: string, applicationId?: string, dockerId: string, command: string }): Promise<any> {
const { execa } = await import('execa')
const { parse } = await import('shell-quote')
let { remoteEngine, remoteIpAddress, engine } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
if (remoteEngine) {
await createRemoteEngineConfiguration(dockerId);
engine = `ssh://${remoteIpAddress}-remote`;
} else {
engine = 'unix:///var/run/docker.sock';
}
if (process.env.CODESANDBOX_HOST) {
if (command.startsWith('docker compose')) {
command = command.replace(/docker compose/gi, 'docker-compose');
}
}
const parsedCommand = parse(command);
const dockerCommand = parsedCommand[0];
const dockerArgs = parsedCommand.slice(1);
if (command.startsWith(`docker build`) || command.startsWith(`pack build`) || command.startsWith(`docker compose build`)) {
return await asyncExecShellStream({ debug, buildId, applicationId, command, engine });
}
return await execa(dockerCommand, dockerArgs, { env: { DOCKER_BUILDKIT: "1", DOCKER_HOST: engine } });
}
export async function startTraefikProxy(id: string): Promise<void> {
const { engine, network, remoteEngine, remoteIpAddress } = await prisma.destinationDocker.findUnique({ where: { id } })
const { found } = await checkContainer({ dockerId: id, container: 'coolify-proxy', remove: true });

View File

@ -32,13 +32,13 @@ export default async function ({
const url = htmlUrl.replace('https://', '').replace('http://', '');
if (forPublic) {
await saveBuildLog({
line: `Cloning ${repository}:${branch} branch`,
line: `Cloning ${repository}:${branch}...`,
buildId,
applicationId
});
if (gitCommitHash) {
await saveBuildLog({
line: `Checking out ${gitCommitHash} commit`,
line: `Checking out ${gitCommitHash} commit...`,
buildId,
applicationId
});
@ -72,13 +72,13 @@ export default async function ({
})
.json();
await saveBuildLog({
line: `Cloning ${repository}:${branch} branch`,
line: `Cloning ${repository}:${branch}...`,
buildId,
applicationId
});
if (gitCommitHash) {
await saveBuildLog({
line: `Checking out ${gitCommitHash} commit`,
line: `Checking out ${gitCommitHash} commit...`,
buildId,
applicationId
});
@ -90,6 +90,5 @@ export default async function ({
});
}
const { stdout: commit } = await executeCommand({ command: `cd ${workdir}/ && git rev-parse HEAD`, shell: true });
console.log({ commit })
return commit.replace('\n', '');
}

View File

@ -1,5 +1,5 @@
import { saveBuildLog } from "../buildPacks/common";
import { executeCommand } from "../common";
import { executeCommand } from "../common";
export default async function ({
applicationId,
@ -33,13 +33,13 @@ export default async function ({
}
await saveBuildLog({
line: `Cloning ${repository}:${branch} branch`,
line: `Cloning ${repository}:${branch}...`,
buildId,
applicationId
});
if (gitCommitHash) {
await saveBuildLog({
line: `Checking out ${gitCommitHash} commit`,
line: `Checking out ${gitCommitHash} commit...`,
buildId,
applicationId
});

View File

@ -4,7 +4,7 @@ import sshConfig from 'ssh-config'
import fs from 'fs/promises'
import os from 'os';
import { createRemoteEngineConfiguration, decrypt, errorHandler, executeCommand, executeSSHCmd, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common';
import { createRemoteEngineConfiguration, decrypt, errorHandler, executeCommand, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common';
import { checkContainer } from '../../../../lib/docker';
import type { OnlyId } from '../../../../types';
@ -216,7 +216,7 @@ export async function verifyRemoteDockerEngineFn(id: string) {
}
if (isCoolifyProxyUsed) await startTraefikProxy(id);
try {
const { stdout: daemonJson } = await executeSSHCmd({ dockerId: id, command: `cat /etc/docker/daemon.json` });
const { stdout: daemonJson } = await executeCommand({ sshCommand: true, dockerId: id, command: `cat /etc/docker/daemon.json` });
let daemonJsonParsed = JSON.parse(daemonJson);
let isUpdated = false;
if (!daemonJsonParsed['live-restore'] || daemonJsonParsed['live-restore'] !== true) {
@ -231,8 +231,8 @@ export async function verifyRemoteDockerEngineFn(id: string) {
}
}
if (isUpdated) {
await executeSSHCmd({ dockerId: id, command: `echo '${JSON.stringify(daemonJsonParsed)}' > /etc/docker/daemon.json` });
await executeSSHCmd({ dockerId: id, command: `systemctl restart docker` });
await executeCommand({ sshCommand: true, shell: true, dockerId: id, command: `echo '${JSON.stringify(daemonJsonParsed)}' > /etc/docker/daemon.json` });
await executeCommand({ sshCommand: true, dockerId: id, command: `systemctl restart docker` });
}
} catch (error) {
const daemonJsonParsed = {
@ -241,8 +241,8 @@ export async function verifyRemoteDockerEngineFn(id: string) {
"buildkit": true
}
}
await executeSSHCmd({ dockerId: id, command: `echo '${JSON.stringify(daemonJsonParsed)}' > /etc/docker/daemon.json` });
await executeSSHCmd({ dockerId: id, command: `systemctl restart docker` });
await executeCommand({ sshCommand: true, shell: true, dockerId: id, command: `echo '${JSON.stringify(daemonJsonParsed)}' > /etc/docker/daemon.json` });
await executeCommand({ sshCommand: true, dockerId: id, command: `systemctl restart docker` });
} finally {
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } })
}

View File

@ -13,7 +13,6 @@ import {
uniqueName,
version,
sentryDSN,
executeDockerCmd,
executeCommand,
} from "../../../lib/common";
import { scheduler } from "../../../lib/scheduler";

View File

@ -1,5 +1,5 @@
import type { FastifyRequest } from 'fastify';
import { errorHandler, prisma, executeSSHCmd } from '../../../../lib/common';
import { errorHandler, prisma, executeCommand } from '../../../../lib/common';
import os from 'node:os';
import osu from 'node-os-utils';
@ -71,10 +71,10 @@ export async function showUsage(request: FastifyRequest) {
let { remoteEngine } = request.query
remoteEngine = remoteEngine === 'true' ? true : false
if (remoteEngine) {
const { stdout: stats } = await executeSSHCmd({ dockerId: id, command: `vmstat -s` })
const { stdout: disks } = await executeSSHCmd({ dockerId: id, command: `df -m / --output=size,used,pcent|grep -v 'Used'| xargs` })
const { stdout: cpus } = await executeSSHCmd({ dockerId: id, command: `nproc --all` })
const { stdout: cpuUsage } = await executeSSHCmd({ dockerId: id, command: `echo $[100-$(vmstat 1 2|tail -1|awk '{print $15}')]` })
const { stdout: stats } = await executeCommand({ sshCommand: true, shell: true, dockerId: id, command: `vmstat -s` })
const { stdout: disks } = await executeCommand({ sshCommand: true, shell: true, dockerId: id, command: `df -m / --output=size,used,pcent|grep -v 'Used'| xargs` })
const { stdout: cpus } = await executeCommand({ sshCommand: true, shell: true, dockerId: id, command: `nproc --all` })
const { stdout: cpuUsage } = await executeCommand({ sshCommand: true, shell: true, dockerId: id, command: `echo $[100-$(vmstat 1 2|tail -1|awk '{print $15}')]` })
const parsed: any = parseFromText(stats)
return {
usage: {

View File

@ -83,53 +83,56 @@ export async function getServiceStatus(request: FastifyRequest<OnlyId>) {
command:
`docker ps -a --filter "label=com.docker.compose.project=${id}" --format '{{json .}}'`
});
const containersArray = containers.trim().split('\n');
if (containersArray.length > 0 && containersArray[0] !== '') {
const templates = await getTemplates();
let template = templates.find(t => t.type === service.type);
const templateStr = JSON.stringify(template)
if (templateStr) {
template = JSON.parse(templateStr.replaceAll('$$id', service.id));
}
for (const container of containersArray) {
let isRunning = false;
let isExited = false;
let isRestarting = false;
let isExcluded = false;
const containerObj = JSON.parse(container);
const exclude = template?.services[containerObj.Names]?.exclude;
if (exclude) {
if (containers) {
const containersArray = containers.trim().split('\n');
if (containersArray.length > 0 && containersArray[0] !== '') {
const templates = await getTemplates();
let template = templates.find(t => t.type === service.type);
const templateStr = JSON.stringify(template)
if (templateStr) {
template = JSON.parse(templateStr.replaceAll('$$id', service.id));
}
for (const container of containersArray) {
let isRunning = false;
let isExited = false;
let isRestarting = false;
let isExcluded = false;
const containerObj = JSON.parse(container);
const exclude = template?.services[containerObj.Names]?.exclude;
if (exclude) {
payload[containerObj.Names] = {
status: {
isExcluded: true,
isRunning: false,
isExited: false,
isRestarting: false,
}
}
continue;
}
const status = containerObj.State
if (status === 'running') {
isRunning = true;
}
if (status === 'exited') {
isExited = true;
}
if (status === 'restarting') {
isRestarting = true;
}
payload[containerObj.Names] = {
status: {
isExcluded: true,
isRunning: false,
isExited: false,
isRestarting: false,
isExcluded,
isRunning,
isExited,
isRestarting
}
}
continue;
}
const status = containerObj.State
if (status === 'running') {
isRunning = true;
}
if (status === 'exited') {
isExited = true;
}
if (status === 'restarting') {
isRestarting = true;
}
payload[containerObj.Names] = {
status: {
isExcluded,
isRunning,
isExited,
isRestarting
}
}
}
}
}
return payload
} catch ({ status, message }) {

View File

@ -167,7 +167,8 @@
}
}
async function getStatus() {
if (($status.application.loading && stopping) || $status.application.restarting === true) return;
if (($status.application.loading && stopping) || $status.application.restarting === true)
return;
$status.application.loading = true;
const data = await get(`/applications/${id}/status`);
@ -453,7 +454,7 @@
<button
class="btn btn-sm gap-2"
disabled={!$isDeploymentEnabled}
on:click={() => handleDeploySubmit(false)}
on:click={() => handleDeploySubmit(true)}
>
{#if $status.application.overallStatus !== 'degraded'}
<svg