fix: harder to remove destinations and sources

This commit is contained in:
Andras Bacsai
2023-04-03 09:55:13 +02:00
parent cb980fb814
commit 4c8e73ac86
2 changed files with 518 additions and 404 deletions

View File

@@ -1,20 +1,29 @@
import type { FastifyRequest } from 'fastify';
import { FastifyReply } from 'fastify';
import sshConfig from 'ssh-config'
import fs from 'fs/promises'
import os from 'os';
import { createRemoteEngineConfiguration, decrypt, errorHandler, executeCommand, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common';
import {
errorHandler,
executeCommand,
listSettings,
prisma,
startTraefikProxy,
stopTraefikProxy
} from '../../../../lib/common';
import { checkContainer } from '../../../../lib/docker';
import type { OnlyId } from '../../../../types';
import type { CheckDestination, ListDestinations, NewDestination, Proxy, SaveDestinationSettings } from './types';
import type {
CheckDestination,
ListDestinations,
NewDestination,
Proxy,
SaveDestinationSettings
} from './types';
export async function listDestinations(request: FastifyRequest<ListDestinations>) {
try {
const teamId = request.user.teamId;
const { onlyVerified = false } = request.query
let destinations = []
const { onlyVerified = false } = request.query;
let destinations = [];
if (teamId === '0') {
destinations = await prisma.destinationDocker.findMany({ include: { teams: true } });
} else {
@@ -24,13 +33,16 @@ export async function listDestinations(request: FastifyRequest<ListDestinations>
});
}
if (onlyVerified) {
destinations = destinations.filter(destination => destination.engine || (destination.remoteEngine && destination.remoteVerified))
destinations = destinations.filter(
(destination) =>
destination.engine || (destination.remoteEngine && destination.remoteVerified)
);
}
return {
destinations
}
};
} catch ({ status, message }) {
return errorHandler({ status, message })
return errorHandler({ status, message });
}
}
export async function checkDestination(request: FastifyRequest<CheckDestination>) {
@@ -42,14 +54,14 @@ export async function checkDestination(request: FastifyRequest<CheckDestination>
message: `Network already exists: ${network}`
};
}
return {}
return {};
} catch ({ status, message }) {
return errorHandler({ status, message })
return errorHandler({ status, message });
}
}
export async function getDestination(request: FastifyRequest<OnlyId>) {
try {
const { id } = request.params
const { id } = request.params;
const teamId = request.user?.teamId;
const destination = await prisma.destinationDocker.findFirst({
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
@@ -66,20 +78,22 @@ export async function getDestination(request: FastifyRequest<OnlyId>) {
return {
...payload
};
} catch ({ status, message }) {
return errorHandler({ status, message })
return errorHandler({ status, message });
}
}
export async function newDestination(request: FastifyRequest<NewDestination>, reply: FastifyReply) {
try {
const teamId = request.user.teamId;
const { id } = request.params
const { id } = request.params;
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } = request.body
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } =
request.body;
if (id === 'new') {
if (engine) {
const { stdout } = await await executeCommand({ command: `docker network ls --filter 'name=^${network}$' --format '{{json .}}'` });
const { stdout } = await await executeCommand({
command: `docker network ls --filter 'name=^${network}$' --format '{{json .}}'`
});
if (stdout === '') {
await await executeCommand({ command: `docker network create --attachable ${network}` });
}
@@ -90,12 +104,16 @@ export async function newDestination(request: FastifyRequest<NewDestination>, re
const destination = destinations.find((destination) => destination.network === network);
if (destinations.length > 0) {
const proxyConfigured = destinations.find(
(destination) => destination.network !== network && destination.isCoolifyProxyUsed === true
(destination) =>
destination.network !== network && destination.isCoolifyProxyUsed === true
);
if (proxyConfigured) {
isCoolifyProxyUsed = !!proxyConfigured.isCoolifyProxyUsed;
}
await prisma.destinationDocker.updateMany({ where: { engine }, data: { isCoolifyProxyUsed } });
await prisma.destinationDocker.updateMany({
where: { engine },
data: { isCoolifyProxyUsed }
});
}
if (isCoolifyProxyUsed) {
await startTraefikProxy(destination.id);
@@ -103,39 +121,60 @@ export async function newDestination(request: FastifyRequest<NewDestination>, re
return reply.code(201).send({ id: destination.id });
} else {
const destination = await prisma.destinationDocker.create({
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed, remoteEngine: true, remoteIpAddress, remoteUser, remotePort: Number(remotePort) }
data: {
name,
teams: { connect: { id: teamId } },
engine,
network,
isCoolifyProxyUsed,
remoteEngine: true,
remoteIpAddress,
remoteUser,
remotePort: Number(remotePort)
}
});
return reply.code(201).send({ id: destination.id })
return reply.code(201).send({ id: destination.id });
}
} else {
await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } });
return reply.code(201).send();
}
} catch ({ status, message }) {
return errorHandler({ status, message })
return errorHandler({ status, message });
}
}
export async function deleteDestination(request: FastifyRequest<OnlyId>) {
try {
const { id } = request.params
const { network, remoteVerified, engine, isCoolifyProxyUsed } = await prisma.destinationDocker.findUnique({ where: { id } });
const { id } = request.params;
const appFound = await prisma.application.findFirst({ where: { destinationDockerId: id } });
const serviceFound = await prisma.service.findFirst({ where: { destinationDockerId: id } });
const databaseFound = await prisma.database.findFirst({ where: { destinationDockerId: id } });
if (appFound || serviceFound || databaseFound) {
throw {
message: `Destination is in use.<br>Remove all applications, services and databases using this destination first.`
};
}
const { network, remoteVerified, engine, isCoolifyProxyUsed } =
await prisma.destinationDocker.findUnique({ where: { id } });
if (isCoolifyProxyUsed) {
if (engine || remoteVerified) {
const { stdout: found } = await executeCommand({
dockerId: id,
command: `docker ps -a --filter network=${network} --filter name=coolify-proxy --format '{{.}}'`
})
});
if (found) {
await executeCommand({ dockerId: id, command: `docker network disconnect ${network} coolify-proxy` })
await executeCommand({ dockerId: id, command: `docker network rm ${network}` })
await executeCommand({
dockerId: id,
command: `docker network disconnect ${network} coolify-proxy`
});
await executeCommand({ dockerId: id, command: `docker network rm ${network}` });
}
}
}
await prisma.destinationDocker.delete({ where: { id } });
return {}
return {};
} catch ({ status, message }) {
return errorHandler({ status, message })
return errorHandler({ status, message });
}
}
export async function saveDestinationSettings(request: FastifyRequest<SaveDestinationSettings>) {
@@ -148,33 +187,33 @@ export async function saveDestinationSettings(request: FastifyRequest<SaveDestin
return {
status: 202
}
};
// return reply.code(201).send();
} catch ({ status, message }) {
return errorHandler({ status, message })
return errorHandler({ status, message });
}
}
export async function startProxy(request: FastifyRequest<Proxy>) {
const { id } = request.params
const { id } = request.params;
try {
await startTraefikProxy(id);
return {}
return {};
} catch ({ status, message }) {
await stopTraefikProxy(id);
return errorHandler({ status, message })
return errorHandler({ status, message });
}
}
export async function stopProxy(request: FastifyRequest<Proxy>) {
const { id } = request.params
const { id } = request.params;
try {
await stopTraefikProxy(id);
return {}
return {};
} catch ({ status, message }) {
return errorHandler({ status, message })
return errorHandler({ status, message });
}
}
export async function restartProxy(request: FastifyRequest<Proxy>) {
const { id } = request.params
const { id } = request.params;
try {
await stopTraefikProxy(id);
await startTraefikProxy(id);
@@ -182,13 +221,13 @@ export async function restartProxy(request: FastifyRequest<Proxy>) {
where: { id },
data: { isCoolifyProxyUsed: true }
});
return {}
return {};
} catch ({ status, message }) {
await prisma.destinationDocker.update({
where: { id },
data: { isCoolifyProxyUsed: false }
});
return errorHandler({ status, message })
return errorHandler({ status, message });
}
}
@@ -196,84 +235,119 @@ export async function assignSSHKey(request: FastifyRequest) {
try {
const { id: sshKeyId } = request.body;
const { id } = request.params;
await prisma.destinationDocker.update({ where: { id }, data: { sshKey: { connect: { id: sshKeyId } } } })
return {}
await prisma.destinationDocker.update({
where: { id },
data: { sshKey: { connect: { id: sshKeyId } } }
});
return {};
} catch ({ status, message }) {
return errorHandler({ status, message })
return errorHandler({ status, message });
}
}
export async function verifyRemoteDockerEngineFn(id: string) {
const { remoteIpAddress, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst({ where: { id } })
const daemonJson = `daemon-${id}.json`
const { remoteIpAddress, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst(
{ where: { id } }
);
const daemonJson = `daemon-${id}.json`;
try {
await executeCommand({ sshCommand: true, command: `docker network inspect ${network}`, dockerId: id });
await executeCommand({
sshCommand: true,
command: `docker network inspect ${network}`,
dockerId: id
});
} catch (error) {
await executeCommand({ command: `docker network create --attachable ${network}`, dockerId: id });
await executeCommand({
command: `docker network create --attachable ${network}`,
dockerId: id
});
}
try {
await executeCommand({ sshCommand: true, command: `docker network inspect coolify-infra`, dockerId: id });
await executeCommand({
sshCommand: true,
command: `docker network inspect coolify-infra`,
dockerId: id
});
} catch (error) {
await executeCommand({ command: `docker network create --attachable coolify-infra`, dockerId: id });
await executeCommand({
command: `docker network create --attachable coolify-infra`,
dockerId: id
});
}
if (isCoolifyProxyUsed) await startTraefikProxy(id);
let isUpdated = false;
let daemonJsonParsed = {
"live-restore": true,
"features": {
"buildkit": true
'live-restore': true,
features: {
buildkit: true
}
};
try {
const { stdout: daemonJson } = await executeCommand({ sshCommand: true, dockerId: id, command: `cat /etc/docker/daemon.json` });
const { stdout: daemonJson } = await executeCommand({
sshCommand: true,
dockerId: id,
command: `cat /etc/docker/daemon.json`
});
daemonJsonParsed = JSON.parse(daemonJson);
if (!daemonJsonParsed['live-restore'] || daemonJsonParsed['live-restore'] !== true) {
isUpdated = true;
daemonJsonParsed['live-restore'] = true
daemonJsonParsed['live-restore'] = true;
}
if (!daemonJsonParsed?.features?.buildkit) {
isUpdated = true;
daemonJsonParsed.features = {
buildkit: true
}
};
}
} catch (error) {
isUpdated = true;
}
try {
if (isUpdated) {
await executeCommand({ shell: true, command: `echo '${JSON.stringify(daemonJsonParsed, null, 2)}' > /tmp/${daemonJson}` })
await executeCommand({ dockerId: id, command: `scp /tmp/${daemonJson} ${remoteIpAddress}-remote:/etc/docker/daemon.json` });
await executeCommand({ command: `rm /tmp/${daemonJson}` })
await executeCommand({
shell: true,
command: `echo '${JSON.stringify(daemonJsonParsed, null, 2)}' > /tmp/${daemonJson}`
});
await executeCommand({
dockerId: id,
command: `scp /tmp/${daemonJson} ${remoteIpAddress}-remote:/etc/docker/daemon.json`
});
await executeCommand({ command: `rm /tmp/${daemonJson}` });
await executeCommand({ sshCommand: true, dockerId: id, command: `systemctl restart docker` });
}
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } })
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } });
} catch (error) {
throw new Error('Error while verifying remote docker engine')
throw new Error('Error while verifying remote docker engine');
}
}
export async function verifyRemoteDockerEngine(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
export async function verifyRemoteDockerEngine(
request: FastifyRequest<OnlyId>,
reply: FastifyReply
) {
const { id } = request.params;
try {
await verifyRemoteDockerEngineFn(id);
return reply.code(201).send()
return reply.code(201).send();
} catch ({ status, message }) {
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: false } })
return errorHandler({ status, message })
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: false } });
return errorHandler({ status, message });
}
}
export async function getDestinationStatus(request: FastifyRequest<OnlyId>) {
try {
const { id } = request.params
const destination = await prisma.destinationDocker.findUnique({ where: { id } })
const { found: isRunning } = await checkContainer({ dockerId: destination.id, container: 'coolify-proxy', remove: true })
const { id } = request.params;
const destination = await prisma.destinationDocker.findUnique({ where: { id } });
const { found: isRunning } = await checkContainer({
dockerId: destination.id,
container: 'coolify-proxy',
remove: true
});
return {
isRunning
}
};
} catch ({ status, message }) {
return errorHandler({ status, message })
return errorHandler({ status, message });
}
}

View File

@@ -1,6 +1,5 @@
import cuid from 'cuid';
import type { FastifyRequest } from 'fastify';
import { FastifyReply } from 'fastify';
import { decrypt, encrypt, errorHandler, prisma } from '../../../../lib/common';
import { OnlyId } from '../../../../types';
import { CheckGitLabOAuthId, SaveGitHubSource, SaveGitLabSource } from './types';
@@ -9,34 +8,39 @@ export async function listSources(request: FastifyRequest) {
try {
const teamId = request.user?.teamId;
const sources = await prisma.gitSource.findMany({
where: { OR: [{ teams: { some: { id: teamId === "0" ? undefined : teamId } } }, { isSystemWide: true }] },
where: {
OR: [
{ teams: { some: { id: teamId === '0' ? undefined : teamId } } },
{ isSystemWide: true }
]
},
include: { teams: true, githubApp: true, gitlabApp: true }
});
return {
sources
}
};
} catch ({ status, message }) {
return errorHandler({ status, message })
return errorHandler({ status, message });
}
}
export async function saveSource(request, reply) {
try {
const { id } = request.params
let { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide } = request.body
if (customPort) customPort = Number(customPort)
const { id } = request.params;
let { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide } = request.body;
if (customPort) customPort = Number(customPort);
await prisma.gitSource.update({
where: { id },
data: { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide }
});
return reply.code(201).send()
return reply.code(201).send();
} catch ({ status, message }) {
return errorHandler({ status, message })
return errorHandler({ status, message });
}
}
export async function getSource(request: FastifyRequest<OnlyId>) {
try {
const { id } = request.params
const { teamId } = request.user
const { id } = request.params;
const { teamId } = request.user;
const settings = await prisma.setting.findFirst({});
if (id === 'new') {
@@ -48,40 +52,54 @@ export async function getSource(request: FastifyRequest<OnlyId>) {
apiUrl: null,
organization: null,
customPort: 22,
customUser: 'git',
customUser: 'git'
},
settings
}
};
}
const source = await prisma.gitSource.findFirst({
where: { id, OR: [{ teams: { some: { id: teamId === "0" ? undefined : teamId } } }, { isSystemWide: true }] },
where: {
id,
OR: [
{ teams: { some: { id: teamId === '0' ? undefined : teamId } } },
{ isSystemWide: true }
]
},
include: { githubApp: true, gitlabApp: true }
});
if (!source) {
throw { status: 404, message: 'Source not found.' }
throw { status: 404, message: 'Source not found.' };
}
if (source?.githubApp?.clientSecret)
source.githubApp.clientSecret = decrypt(source.githubApp.clientSecret);
if (source?.githubApp?.webhookSecret)
source.githubApp.webhookSecret = decrypt(source.githubApp.webhookSecret);
if (source?.githubApp?.privateKey) source.githubApp.privateKey = decrypt(source.githubApp.privateKey);
if (source?.gitlabApp?.appSecret) source.gitlabApp.appSecret = decrypt(source.gitlabApp.appSecret);
if (source?.githubApp?.privateKey)
source.githubApp.privateKey = decrypt(source.githubApp.privateKey);
if (source?.gitlabApp?.appSecret)
source.gitlabApp.appSecret = decrypt(source.gitlabApp.appSecret);
return {
source,
settings
};
} catch ({ status, message }) {
return errorHandler({ status, message })
return errorHandler({ status, message });
}
}
export async function deleteSource(request) {
try {
const { id } = request.params
const { id } = request.params;
const gitAppFound = await prisma.application.findFirst({ where: { gitSourceId: id } });
if (gitAppFound) {
throw {
status: 400,
message: 'This source is used by an application. Please remove the application first.'
};
}
const source = await prisma.gitSource.delete({
where: { id },
include: { githubApp: true, gitlabApp: true }
@@ -92,22 +110,21 @@ export async function deleteSource(request) {
if (source.gitlabAppId) {
await prisma.gitlabApp.delete({ where: { id: source.gitlabAppId } });
}
return {}
return {};
} catch ({ status, message }) {
return errorHandler({ status, message })
return errorHandler({ status, message });
}
}
export async function saveGitHubSource(request: FastifyRequest<SaveGitHubSource>) {
try {
const { teamId } = request.user
const { teamId } = request.user;
const { id } = request.params
let { name, htmlUrl, apiUrl, organization, customPort, isSystemWide } = request.body
const { id } = request.params;
let { name, htmlUrl, apiUrl, organization, customPort, isSystemWide } = request.body;
if (customPort) customPort = Number(customPort)
if (customPort) customPort = Number(customPort);
if (id === 'new') {
const newId = cuid()
const newId = cuid();
await prisma.gitSource.create({
data: {
id: newId,
@@ -123,27 +140,48 @@ export async function saveGitHubSource(request: FastifyRequest<SaveGitHubSource>
});
return {
id: newId
};
}
}
throw { status: 500, message: 'Wrong request.' }
throw { status: 500, message: 'Wrong request.' };
} catch ({ status, message }) {
return errorHandler({ status, message })
return errorHandler({ status, message });
}
}
export async function saveGitLabSource(request: FastifyRequest<SaveGitLabSource>) {
try {
const { id } = request.params
const { teamId } = request.user
let { type, name, htmlUrl, apiUrl, oauthId, appId, appSecret, groupName, customPort, customUser } =
request.body
const { id } = request.params;
const { teamId } = request.user;
let {
type,
name,
htmlUrl,
apiUrl,
oauthId,
appId,
appSecret,
groupName,
customPort,
customUser
} = request.body;
if (oauthId) oauthId = Number(oauthId);
if (customPort) customPort = Number(customPort)
if (customPort) customPort = Number(customPort);
const encryptedAppSecret = encrypt(appSecret);
if (id === 'new') {
const newId = cuid()
await prisma.gitSource.create({ data: { id: newId, type, apiUrl, htmlUrl, name, customPort, customUser, teams: { connect: { id: teamId } } } });
const newId = cuid();
await prisma.gitSource.create({
data: {
id: newId,
type,
apiUrl,
htmlUrl,
name,
customPort,
customUser,
teams: { connect: { id: teamId } }
}
});
await prisma.gitlabApp.create({
data: {
teams: { connect: { id: teamId } },
@@ -157,35 +195,37 @@ export async function saveGitLabSource(request: FastifyRequest<SaveGitLabSource>
return {
status: 201,
id: newId
}
};
} else {
await prisma.gitSource.update({ where: { id }, data: { type, apiUrl, htmlUrl, name, customPort, customUser } });
await prisma.gitSource.update({
where: { id },
data: { type, apiUrl, htmlUrl, name, customPort, customUser }
});
await prisma.gitlabApp.update({
where: { id },
data: {
appId,
oauthId,
groupName,
appSecret: encryptedAppSecret,
appSecret: encryptedAppSecret
}
});
}
return { status: 201 };
} catch ({ status, message }) {
return errorHandler({ status, message })
return errorHandler({ status, message });
}
}
export async function checkGitLabOAuthID(request: FastifyRequest<CheckGitLabOAuthId>) {
try {
const { oauthId } = request.body
const { oauthId } = request.body;
const found = await prisma.gitlabApp.findFirst({ where: { oauthId: Number(oauthId) } });
if (found) {
throw { status: 500, message: 'OAuthID already configured in Coolify.' }
throw { status: 500, message: 'OAuthID already configured in Coolify.' };
}
return {}
return {};
} catch ({ status, message }) {
return errorHandler({ status, message })
return errorHandler({ status, message });
}
}