598 lines
16 KiB
TypeScript
598 lines
16 KiB
TypeScript
import { z } from 'zod';
|
|
import { privateProcedure, router } from '../trpc';
|
|
import { decrypt, isARM } from '../../lib/common';
|
|
import { prisma } from '../../prisma';
|
|
import { executeCommand } from '../../lib/executeCommand';
|
|
import { checkContainer, removeContainer } from '../../lib/docker';
|
|
|
|
export const applicationsRouter = router({
|
|
status: privateProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
|
|
const id: string = input.id;
|
|
const teamId = ctx.user?.teamId;
|
|
if (!teamId) {
|
|
throw { status: 400, message: 'Team not found.' };
|
|
}
|
|
let payload = [];
|
|
const application: any = await getApplicationFromDB(id, teamId);
|
|
if (application?.destinationDockerId) {
|
|
if (application.buildPack === 'compose') {
|
|
const { stdout: containers } = await executeCommand({
|
|
dockerId: application.destinationDocker.id,
|
|
command: `docker ps -a --filter "label=coolify.applicationId=${id}" --format '{{json .}}'`
|
|
});
|
|
const containersArray = containers.trim().split('\n');
|
|
if (containersArray.length > 0 && containersArray[0] !== '') {
|
|
for (const container of containersArray) {
|
|
let isRunning = false;
|
|
let isExited = false;
|
|
let isRestarting = false;
|
|
const containerObj = JSON.parse(container);
|
|
const status = containerObj.State;
|
|
if (status === 'running') {
|
|
isRunning = true;
|
|
}
|
|
if (status === 'exited') {
|
|
isExited = true;
|
|
}
|
|
if (status === 'restarting') {
|
|
isRestarting = true;
|
|
}
|
|
payload.push({
|
|
name: containerObj.Names,
|
|
status: {
|
|
isRunning,
|
|
isExited,
|
|
isRestarting
|
|
}
|
|
});
|
|
}
|
|
}
|
|
} else {
|
|
let isRunning = false;
|
|
let isExited = false;
|
|
let isRestarting = false;
|
|
const status = await checkContainer({
|
|
dockerId: application.destinationDocker.id,
|
|
container: id
|
|
});
|
|
if (status?.found) {
|
|
isRunning = status.status.isRunning;
|
|
isExited = status.status.isExited;
|
|
isRestarting = status.status.isRestarting;
|
|
payload.push({
|
|
name: id,
|
|
status: {
|
|
isRunning,
|
|
isExited,
|
|
isRestarting
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
return payload;
|
|
}),
|
|
cleanup: privateProcedure.query(async ({ ctx }) => {
|
|
const teamId = ctx.user?.teamId;
|
|
let applications = await prisma.application.findMany({
|
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
|
include: { settings: true, destinationDocker: true, teams: true }
|
|
});
|
|
for (const application of applications) {
|
|
if (
|
|
!application.buildPack ||
|
|
!application.destinationDockerId ||
|
|
!application.branch ||
|
|
(!application.settings?.isBot && !application?.fqdn)
|
|
) {
|
|
if (application?.destinationDockerId && application.destinationDocker?.network) {
|
|
const { stdout: containers } = await executeCommand({
|
|
dockerId: application.destinationDocker.id,
|
|
command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${application.id} --format '{{json .}}'`
|
|
});
|
|
if (containers) {
|
|
const containersArray = containers.trim().split('\n');
|
|
for (const container of containersArray) {
|
|
const containerObj = JSON.parse(container);
|
|
const id = containerObj.ID;
|
|
await removeContainer({ id, dockerId: application.destinationDocker.id });
|
|
}
|
|
}
|
|
}
|
|
await prisma.applicationSettings.deleteMany({ where: { applicationId: application.id } });
|
|
await prisma.buildLog.deleteMany({ where: { applicationId: application.id } });
|
|
await prisma.build.deleteMany({ where: { applicationId: application.id } });
|
|
await prisma.secret.deleteMany({ where: { applicationId: application.id } });
|
|
await prisma.applicationPersistentStorage.deleteMany({
|
|
where: { applicationId: application.id }
|
|
});
|
|
await prisma.applicationConnectedDatabase.deleteMany({
|
|
where: { applicationId: application.id }
|
|
});
|
|
await prisma.application.deleteMany({ where: { id: application.id } });
|
|
}
|
|
}
|
|
return {};
|
|
}),
|
|
delete: privateProcedure
|
|
.input(z.object({ force: z.boolean(), id: z.string() }))
|
|
.mutation(async ({ ctx, input }) => {
|
|
const { id, force } = input;
|
|
const teamId = ctx.user?.teamId;
|
|
const application = await prisma.application.findUnique({
|
|
where: { id },
|
|
include: { destinationDocker: true }
|
|
});
|
|
if (!force && application?.destinationDockerId && application.destinationDocker?.network) {
|
|
const { stdout: containers } = await executeCommand({
|
|
dockerId: application.destinationDocker.id,
|
|
command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${id} --format '{{json .}}'`
|
|
});
|
|
if (containers) {
|
|
const containersArray = containers.trim().split('\n');
|
|
for (const container of containersArray) {
|
|
const containerObj = JSON.parse(container);
|
|
const id = containerObj.ID;
|
|
await removeContainer({ id, dockerId: application.destinationDocker.id });
|
|
}
|
|
}
|
|
}
|
|
await prisma.applicationSettings.deleteMany({ where: { application: { id } } });
|
|
await prisma.buildLog.deleteMany({ where: { applicationId: id } });
|
|
await prisma.build.deleteMany({ where: { applicationId: id } });
|
|
await prisma.secret.deleteMany({ where: { applicationId: id } });
|
|
await prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: id } });
|
|
await prisma.applicationConnectedDatabase.deleteMany({ where: { applicationId: id } });
|
|
if (teamId === '0') {
|
|
await prisma.application.deleteMany({ where: { id } });
|
|
} else {
|
|
await prisma.application.deleteMany({ where: { id, teams: { some: { id: teamId } } } });
|
|
}
|
|
return {};
|
|
})
|
|
});
|
|
|
|
export async function getApplicationFromDB(id: string, teamId: string) {
|
|
let application = await prisma.application.findFirst({
|
|
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
|
include: {
|
|
destinationDocker: true,
|
|
settings: true,
|
|
gitSource: { include: { githubApp: true, gitlabApp: true } },
|
|
secrets: true,
|
|
persistentStorage: true,
|
|
connectedDatabase: true,
|
|
previewApplication: true,
|
|
dockerRegistry: true
|
|
}
|
|
});
|
|
if (!application) {
|
|
throw { status: 404, message: 'Application not found.' };
|
|
}
|
|
application = decryptApplication(application);
|
|
const buildPack = application?.buildPack || null;
|
|
const { baseImage, baseBuildImage, baseBuildImages, baseImages } = setDefaultBaseImage(buildPack);
|
|
|
|
// Set default build images
|
|
if (application && !application.baseImage) {
|
|
application.baseImage = baseImage;
|
|
}
|
|
if (application && !application.baseBuildImage) {
|
|
application.baseBuildImage = baseBuildImage;
|
|
}
|
|
return { ...application, baseBuildImages, baseImages };
|
|
}
|
|
function decryptApplication(application: any) {
|
|
if (application) {
|
|
if (application?.gitSource?.githubApp?.clientSecret) {
|
|
application.gitSource.githubApp.clientSecret =
|
|
decrypt(application.gitSource.githubApp.clientSecret) || null;
|
|
}
|
|
if (application?.gitSource?.githubApp?.webhookSecret) {
|
|
application.gitSource.githubApp.webhookSecret =
|
|
decrypt(application.gitSource.githubApp.webhookSecret) || null;
|
|
}
|
|
if (application?.gitSource?.githubApp?.privateKey) {
|
|
application.gitSource.githubApp.privateKey =
|
|
decrypt(application.gitSource.githubApp.privateKey) || null;
|
|
}
|
|
if (application?.gitSource?.gitlabApp?.appSecret) {
|
|
application.gitSource.gitlabApp.appSecret =
|
|
decrypt(application.gitSource.gitlabApp.appSecret) || null;
|
|
}
|
|
if (application?.secrets.length > 0) {
|
|
application.secrets = application.secrets.map((s: any) => {
|
|
s.value = decrypt(s.value) || null;
|
|
return s;
|
|
});
|
|
}
|
|
|
|
return application;
|
|
}
|
|
}
|
|
|
|
const staticApps = ['static', 'react', 'vuejs', 'svelte', 'gatsby', 'astro', 'eleventy'];
|
|
const nodeBased = [
|
|
'react',
|
|
'preact',
|
|
'vuejs',
|
|
'svelte',
|
|
'gatsby',
|
|
'astro',
|
|
'eleventy',
|
|
'node',
|
|
'nestjs',
|
|
'nuxtjs',
|
|
'nextjs'
|
|
];
|
|
export function setDefaultBaseImage(
|
|
buildPack: string | null,
|
|
deploymentType: string | null = null
|
|
) {
|
|
const nodeVersions = [
|
|
{
|
|
value: 'node:lts',
|
|
label: 'node:lts'
|
|
},
|
|
{
|
|
value: 'node:18',
|
|
label: 'node:18'
|
|
},
|
|
{
|
|
value: 'node:17',
|
|
label: 'node:17'
|
|
},
|
|
{
|
|
value: 'node:16',
|
|
label: 'node:16'
|
|
},
|
|
{
|
|
value: 'node:14',
|
|
label: 'node:14'
|
|
},
|
|
{
|
|
value: 'node:12',
|
|
label: 'node:12'
|
|
}
|
|
];
|
|
const staticVersions = [
|
|
{
|
|
value: 'webdevops/nginx:alpine',
|
|
label: 'webdevops/nginx:alpine'
|
|
},
|
|
{
|
|
value: 'webdevops/apache:alpine',
|
|
label: 'webdevops/apache:alpine'
|
|
},
|
|
{
|
|
value: 'nginx:alpine',
|
|
label: 'nginx:alpine'
|
|
},
|
|
{
|
|
value: 'httpd:alpine',
|
|
label: 'httpd:alpine (Apache)'
|
|
}
|
|
];
|
|
const rustVersions = [
|
|
{
|
|
value: 'rust:latest',
|
|
label: 'rust:latest'
|
|
},
|
|
{
|
|
value: 'rust:1.60',
|
|
label: 'rust:1.60'
|
|
},
|
|
{
|
|
value: 'rust:1.60-buster',
|
|
label: 'rust:1.60-buster'
|
|
},
|
|
{
|
|
value: 'rust:1.60-bullseye',
|
|
label: 'rust:1.60-bullseye'
|
|
},
|
|
{
|
|
value: 'rust:1.60-slim-buster',
|
|
label: 'rust:1.60-slim-buster'
|
|
},
|
|
{
|
|
value: 'rust:1.60-slim-bullseye',
|
|
label: 'rust:1.60-slim-bullseye'
|
|
},
|
|
{
|
|
value: 'rust:1.60-alpine3.14',
|
|
label: 'rust:1.60-alpine3.14'
|
|
},
|
|
{
|
|
value: 'rust:1.60-alpine3.15',
|
|
label: 'rust:1.60-alpine3.15'
|
|
}
|
|
];
|
|
const phpVersions = [
|
|
{
|
|
value: 'webdevops/php-apache:8.2',
|
|
label: 'webdevops/php-apache:8.2'
|
|
},
|
|
{
|
|
value: 'webdevops/php-nginx:8.2',
|
|
label: 'webdevops/php-nginx:8.2'
|
|
},
|
|
{
|
|
value: 'webdevops/php-apache:8.1',
|
|
label: 'webdevops/php-apache:8.1'
|
|
},
|
|
{
|
|
value: 'webdevops/php-nginx:8.1',
|
|
label: 'webdevops/php-nginx:8.1'
|
|
},
|
|
{
|
|
value: 'webdevops/php-apache:8.0',
|
|
label: 'webdevops/php-apache:8.0'
|
|
},
|
|
{
|
|
value: 'webdevops/php-nginx:8.0',
|
|
label: 'webdevops/php-nginx:8.0'
|
|
},
|
|
{
|
|
value: 'webdevops/php-apache:7.4',
|
|
label: 'webdevops/php-apache:7.4'
|
|
},
|
|
{
|
|
value: 'webdevops/php-nginx:7.4',
|
|
label: 'webdevops/php-nginx:7.4'
|
|
},
|
|
{
|
|
value: 'webdevops/php-apache:7.3',
|
|
label: 'webdevops/php-apache:7.3'
|
|
},
|
|
{
|
|
value: 'webdevops/php-nginx:7.3',
|
|
label: 'webdevops/php-nginx:7.3'
|
|
},
|
|
{
|
|
value: 'webdevops/php-apache:7.2',
|
|
label: 'webdevops/php-apache:7.2'
|
|
},
|
|
{
|
|
value: 'webdevops/php-nginx:7.2',
|
|
label: 'webdevops/php-nginx:7.2'
|
|
},
|
|
{
|
|
value: 'webdevops/php-apache:7.1',
|
|
label: 'webdevops/php-apache:7.1'
|
|
},
|
|
{
|
|
value: 'webdevops/php-nginx:7.1',
|
|
label: 'webdevops/php-nginx:7.1'
|
|
},
|
|
{
|
|
value: 'webdevops/php-apache:7.0',
|
|
label: 'webdevops/php-apache:7.0'
|
|
},
|
|
{
|
|
value: 'webdevops/php-nginx:7.0',
|
|
label: 'webdevops/php-nginx:7.0'
|
|
},
|
|
{
|
|
value: 'webdevops/php-apache:5.6',
|
|
label: 'webdevops/php-apache:5.6'
|
|
},
|
|
{
|
|
value: 'webdevops/php-nginx:5.6',
|
|
label: 'webdevops/php-nginx:5.6'
|
|
},
|
|
{
|
|
value: 'webdevops/php-apache:8.2-alpine',
|
|
label: 'webdevops/php-apache:8.2-alpine'
|
|
},
|
|
{
|
|
value: 'webdevops/php-nginx:8.2-alpine',
|
|
label: 'webdevops/php-nginx:8.2-alpine'
|
|
},
|
|
{
|
|
value: 'webdevops/php-apache:8.1-alpine',
|
|
label: 'webdevops/php-apache:8.1-alpine'
|
|
},
|
|
{
|
|
value: 'webdevops/php-nginx:8.1-alpine',
|
|
label: 'webdevops/php-nginx:8.1-alpine'
|
|
},
|
|
{
|
|
value: 'webdevops/php-apache:8.0-alpine',
|
|
label: 'webdevops/php-apache:8.0-alpine'
|
|
},
|
|
{
|
|
value: 'webdevops/php-nginx:8.0-alpine',
|
|
label: 'webdevops/php-nginx:8.0-alpine'
|
|
},
|
|
{
|
|
value: 'webdevops/php-apache:7.4-alpine',
|
|
label: 'webdevops/php-apache:7.4-alpine'
|
|
},
|
|
{
|
|
value: 'webdevops/php-nginx:7.4-alpine',
|
|
label: 'webdevops/php-nginx:7.4-alpine'
|
|
},
|
|
{
|
|
value: 'webdevops/php-apache:7.3-alpine',
|
|
label: 'webdevops/php-apache:7.3-alpine'
|
|
},
|
|
{
|
|
value: 'webdevops/php-nginx:7.3-alpine',
|
|
label: 'webdevops/php-nginx:7.3-alpine'
|
|
},
|
|
{
|
|
value: 'webdevops/php-apache:7.2-alpine',
|
|
label: 'webdevops/php-apache:7.2-alpine'
|
|
},
|
|
{
|
|
value: 'webdevops/php-nginx:7.2-alpine',
|
|
label: 'webdevops/php-nginx:7.2-alpine'
|
|
},
|
|
{
|
|
value: 'webdevops/php-apache:7.1-alpine',
|
|
label: 'webdevops/php-apache:7.1-alpine'
|
|
},
|
|
{
|
|
value: 'php:8.1-fpm',
|
|
label: 'php:8.1-fpm'
|
|
},
|
|
{
|
|
value: 'php:8.0-fpm',
|
|
label: 'php:8.0-fpm'
|
|
},
|
|
{
|
|
value: 'php:8.1-fpm-alpine',
|
|
label: 'php:8.1-fpm-alpine'
|
|
},
|
|
{
|
|
value: 'php:8.0-fpm-alpine',
|
|
label: 'php:8.0-fpm-alpine'
|
|
}
|
|
];
|
|
const pythonVersions = [
|
|
{
|
|
value: 'python:3.10-alpine',
|
|
label: 'python:3.10-alpine'
|
|
},
|
|
{
|
|
value: 'python:3.10-buster',
|
|
label: 'python:3.10-buster'
|
|
},
|
|
{
|
|
value: 'python:3.10-bullseye',
|
|
label: 'python:3.10-bullseye'
|
|
},
|
|
{
|
|
value: 'python:3.10-slim-bullseye',
|
|
label: 'python:3.10-slim-bullseye'
|
|
},
|
|
{
|
|
value: 'python:3.9-alpine',
|
|
label: 'python:3.9-alpine'
|
|
},
|
|
{
|
|
value: 'python:3.9-buster',
|
|
label: 'python:3.9-buster'
|
|
},
|
|
{
|
|
value: 'python:3.9-bullseye',
|
|
label: 'python:3.9-bullseye'
|
|
},
|
|
{
|
|
value: 'python:3.9-slim-bullseye',
|
|
label: 'python:3.9-slim-bullseye'
|
|
},
|
|
{
|
|
value: 'python:3.8-alpine',
|
|
label: 'python:3.8-alpine'
|
|
},
|
|
{
|
|
value: 'python:3.8-buster',
|
|
label: 'python:3.8-buster'
|
|
},
|
|
{
|
|
value: 'python:3.8-bullseye',
|
|
label: 'python:3.8-bullseye'
|
|
},
|
|
{
|
|
value: 'python:3.8-slim-bullseye',
|
|
label: 'python:3.8-slim-bullseye'
|
|
},
|
|
{
|
|
value: 'python:3.7-alpine',
|
|
label: 'python:3.7-alpine'
|
|
},
|
|
{
|
|
value: 'python:3.7-buster',
|
|
label: 'python:3.7-buster'
|
|
},
|
|
{
|
|
value: 'python:3.7-bullseye',
|
|
label: 'python:3.7-bullseye'
|
|
},
|
|
{
|
|
value: 'python:3.7-slim-bullseye',
|
|
label: 'python:3.7-slim-bullseye'
|
|
}
|
|
];
|
|
const herokuVersions = [
|
|
{
|
|
value: 'heroku/builder:22',
|
|
label: 'heroku/builder:22'
|
|
},
|
|
{
|
|
value: 'heroku/buildpacks:20',
|
|
label: 'heroku/buildpacks:20'
|
|
},
|
|
{
|
|
value: 'heroku/builder-classic:22',
|
|
label: 'heroku/builder-classic:22'
|
|
}
|
|
];
|
|
let payload: any = {
|
|
baseImage: null,
|
|
baseBuildImage: null,
|
|
baseImages: [],
|
|
baseBuildImages: []
|
|
};
|
|
if (nodeBased.includes(buildPack)) {
|
|
if (deploymentType === 'static') {
|
|
payload.baseImage = isARM(process.arch) ? 'nginx:alpine' : 'webdevops/nginx:alpine';
|
|
payload.baseImages = isARM(process.arch)
|
|
? staticVersions.filter((version) => !version.value.includes('webdevops'))
|
|
: staticVersions;
|
|
payload.baseBuildImage = 'node:lts';
|
|
payload.baseBuildImages = nodeVersions;
|
|
} else {
|
|
payload.baseImage = 'node:lts';
|
|
payload.baseImages = nodeVersions;
|
|
payload.baseBuildImage = 'node:lts';
|
|
payload.baseBuildImages = nodeVersions;
|
|
}
|
|
}
|
|
if (staticApps.includes(buildPack)) {
|
|
payload.baseImage = isARM(process.arch) ? 'nginx:alpine' : 'webdevops/nginx:alpine';
|
|
payload.baseImages = isARM(process.arch)
|
|
? staticVersions.filter((version) => !version.value.includes('webdevops'))
|
|
: staticVersions;
|
|
payload.baseBuildImage = 'node:lts';
|
|
payload.baseBuildImages = nodeVersions;
|
|
}
|
|
if (buildPack === 'python') {
|
|
payload.baseImage = 'python:3.10-alpine';
|
|
payload.baseImages = pythonVersions;
|
|
}
|
|
if (buildPack === 'rust') {
|
|
payload.baseImage = 'rust:latest';
|
|
payload.baseBuildImage = 'rust:latest';
|
|
payload.baseImages = rustVersions;
|
|
payload.baseBuildImages = rustVersions;
|
|
}
|
|
if (buildPack === 'deno') {
|
|
payload.baseImage = 'denoland/deno:latest';
|
|
}
|
|
if (buildPack === 'php') {
|
|
payload.baseImage = isARM(process.arch)
|
|
? 'php:8.1-fpm-alpine'
|
|
: 'webdevops/php-apache:8.2-alpine';
|
|
payload.baseImages = isARM(process.arch)
|
|
? phpVersions.filter((version) => !version.value.includes('webdevops'))
|
|
: phpVersions;
|
|
}
|
|
if (buildPack === 'laravel') {
|
|
payload.baseImage = isARM(process.arch)
|
|
? 'php:8.1-fpm-alpine'
|
|
: 'webdevops/php-apache:8.2-alpine';
|
|
payload.baseImages = isARM(process.arch)
|
|
? phpVersions.filter((version) => !version.value.includes('webdevops'))
|
|
: phpVersions;
|
|
payload.baseBuildImage = 'node:18';
|
|
payload.baseBuildImages = nodeVersions;
|
|
}
|
|
if (buildPack === 'heroku') {
|
|
payload.baseImage = 'heroku/buildpacks:20';
|
|
payload.baseImages = herokuVersions;
|
|
}
|
|
return payload;
|
|
}
|