fix: update prisma

feat(beta): database branching
This commit is contained in:
Andras Bacsai
2022-09-05 15:41:32 +02:00
parent c9aecd51f3
commit 5a15e64471
12 changed files with 707 additions and 687 deletions

View File

@@ -15,7 +15,7 @@
},
"dependencies": {
"@breejs/ts-worker": "2.0.0",
"@fastify/autoload": "5.2.0",
"@fastify/autoload": "5.3.1",
"@fastify/cookie": "8.1.0",
"@fastify/cors": "8.1.0",
"@fastify/env": "4.1.0",
@@ -23,7 +23,7 @@
"@fastify/static": "6.5.0",
"@iarna/toml": "2.2.5",
"@ladjs/graceful": "3.0.2",
"@prisma/client": "3.15.2",
"@prisma/client": "4.3.1",
"axios": "0.27.2",
"bcryptjs": "2.4.3",
"bree": "9.1.2",
@@ -37,7 +37,7 @@
"fastify": "4.5.3",
"fastify-plugin": "4.2.1",
"generate-password": "1.7.0",
"got": "12.3.1",
"got": "12.4.1",
"is-ip": "5.0.0",
"is-port-reachable": "4.0.0",
"js-yaml": "4.1.0",
@@ -52,20 +52,20 @@
"unique-names-generator": "4.7.1"
},
"devDependencies": {
"@types/node": "18.7.13",
"@types/node": "18.7.15",
"@types/node-os-utils": "1.3.0",
"@typescript-eslint/eslint-plugin": "5.35.1",
"@typescript-eslint/parser": "5.35.1",
"esbuild": "0.15.5",
"@typescript-eslint/eslint-plugin": "5.36.1",
"@typescript-eslint/parser": "5.36.1",
"esbuild": "0.15.7",
"eslint": "8.23.0",
"eslint-config-prettier": "8.5.0",
"eslint-plugin-prettier": "4.2.1",
"nodemon": "2.0.19",
"prettier": "2.7.1",
"prisma": "3.15.2",
"prisma": "4.3.1",
"rimraf": "3.0.2",
"tsconfig-paths": "4.1.0",
"typescript": "4.7.4"
"typescript": "4.8.2"
},
"prisma": {
"seed": "node prisma/seed.js"

View File

@@ -0,0 +1,20 @@
/*
Warnings:
- You are about to alter the column `time` on the `BuildLog` table. The data in that column could be lost. The data in that column will be cast from `Int` to `BigInt`.
*/
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_BuildLog" (
"id" TEXT NOT NULL PRIMARY KEY,
"applicationId" TEXT,
"buildId" TEXT NOT NULL,
"line" TEXT NOT NULL,
"time" BIGINT NOT NULL
);
INSERT INTO "new_BuildLog" ("applicationId", "buildId", "id", "line", "time") SELECT "applicationId", "buildId", "id", "line", "time" FROM "BuildLog";
DROP TABLE "BuildLog";
ALTER TABLE "new_BuildLog" RENAME TO "BuildLog";
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@@ -0,0 +1,20 @@
-- CreateTable
CREATE TABLE "ApplicationConnectedDatabase" (
"id" TEXT NOT NULL PRIMARY KEY,
"applicationId" TEXT NOT NULL,
"databaseId" TEXT,
"hostedDatabaseType" TEXT,
"hostedDatabaseHost" TEXT,
"hostedDatabasePort" INTEGER,
"hostedDatabaseName" TEXT,
"hostedDatabaseUser" TEXT,
"hostedDatabasePassword" TEXT,
"hostedDatabaseDBName" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "ApplicationConnectedDatabase_databaseId_fkey" FOREIGN KEY ("databaseId") REFERENCES "Database" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT "ApplicationConnectedDatabase_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "ApplicationConnectedDatabase_applicationId_key" ON "ApplicationConnectedDatabase"("applicationId");

View File

@@ -117,6 +117,24 @@ model Application {
settings ApplicationSettings?
secrets Secret[]
teams Team[]
connectedDatabase ApplicationConnectedDatabase?
}
model ApplicationConnectedDatabase {
id String @id @default(cuid())
applicationId String @unique
databaseId String?
hostedDatabaseType String?
hostedDatabaseHost String?
hostedDatabasePort Int?
hostedDatabaseName String?
hostedDatabaseUser String?
hostedDatabasePassword String?
hostedDatabaseDBName String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
database Database? @relation(fields: [databaseId], references: [id])
application Application @relation(fields: [applicationId], references: [id])
}
model ApplicationSettings {
@@ -187,7 +205,7 @@ model BuildLog {
applicationId String?
buildId String
line String
time Int
time BigInt
}
model Build {
@@ -292,22 +310,23 @@ model GitlabApp {
}
model Database {
id String @id @default(cuid())
name String
publicPort Int?
defaultDatabase String?
type String?
version String?
dbUser String?
dbUserPassword String?
rootUser String?
rootUserPassword String?
destinationDockerId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
settings DatabaseSettings?
teams Team[]
id String @id @default(cuid())
name String
publicPort Int?
defaultDatabase String?
type String?
version String?
dbUser String?
dbUserPassword String?
rootUser String?
rootUserPassword String?
destinationDockerId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
settings DatabaseSettings?
teams Team[]
applicationConnectedDatabase ApplicationConnectedDatabase[]
}
model DatabaseSettings {

View File

@@ -156,7 +156,8 @@ export async function getApplicationFromDB(id: string, teamId: string) {
settings: true,
gitSource: { include: { githubApp: true, gitlabApp: true } },
secrets: true,
persistentStorage: true
persistentStorage: true,
connectedDatabase: true
}
});
if (!application) {
@@ -190,7 +191,8 @@ export async function getApplicationFromDBWebhook(projectId: number, branch: str
settings: true,
gitSource: { include: { githubApp: true, gitlabApp: true } },
secrets: true,
persistentStorage: true
persistentStorage: true,
connectedDatabase: true
}
});
if (applications.length === 0) {
@@ -242,7 +244,8 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
denoOptions,
baseImage,
baseBuildImage,
deploymentType
deploymentType,
baseDatabaseBranch
} = request.body
if (port) port = Number(port);
if (exposePort) {
@@ -263,22 +266,43 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
dockerFileLocation,
denoMainFile
});
await prisma.application.update({
where: { id },
data: {
name,
fqdn,
exposePort,
pythonWSGI,
pythonModule,
pythonVariable,
denoOptions,
baseImage,
baseBuildImage,
deploymentType,
...defaultConfiguration
}
});
if (baseDatabaseBranch) {
await prisma.application.update({
where: { id },
data: {
name,
fqdn,
exposePort,
pythonWSGI,
pythonModule,
pythonVariable,
denoOptions,
baseImage,
baseBuildImage,
deploymentType,
...defaultConfiguration,
connectedDatabase: { update: { hostedDatabaseDBName: baseDatabaseBranch } }
}
});
} else {
await prisma.application.update({
where: { id },
data: {
name,
fqdn,
exposePort,
pythonWSGI,
pythonModule,
pythonVariable,
denoOptions,
baseImage,
baseBuildImage,
deploymentType,
...defaultConfiguration
}
});
}
return reply.code(201).send();
} catch ({ status, message }) {
return errorHandler({ status, message })
@@ -289,7 +313,7 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
export async function saveApplicationSettings(request: FastifyRequest<SaveApplicationSettings>, reply: FastifyReply) {
try {
const { id } = request.params
const { debug, previews, dualCerts, autodeploy, branch, projectId, isBot } = request.body
const { debug, previews, dualCerts, autodeploy, branch, projectId, isBot, isDBBranching } = request.body
// const isDouble = await checkDoubleBranch(branch, projectId);
// if (isDouble && autodeploy) {
// await prisma.applicationSettings.updateMany({ where: { application: { branch, projectId } }, data: { autodeploy: false } })
@@ -297,7 +321,7 @@ export async function saveApplicationSettings(request: FastifyRequest<SaveApplic
// }
await prisma.application.update({
where: { id },
data: { fqdn: isBot ? null : undefined, settings: { update: { debug, previews, dualCerts, autodeploy, isBot } } },
data: { fqdn: isBot ? null : undefined, settings: { update: { debug, previews, dualCerts, autodeploy, isBot, isDBBranching } } },
include: { destinationDocker: true }
});
return reply.code(201).send();
@@ -478,6 +502,7 @@ export async function deleteApplication(request: FastifyRequest<DeleteApplicatio
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 {
@@ -738,6 +763,17 @@ export async function saveBuildPack(request, reply) {
return errorHandler({ status, message })
}
}
export async function saveConnectedDatabase(request, reply) {
try {
const { id } = request.params
const { databaseId, type } = request.body
console.log({ databaseId, type })
await prisma.application.update({ where: { id }, data: { connectedDatabase: { upsert: { create: { database: { connect: { id: databaseId } }, hostedDatabaseType: type }, update: { database: { connect: { id: databaseId } }, hostedDatabaseType: type } } } } })
return reply.code(201).send()
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function getSecrets(request: FastifyRequest<OnlyId>) {
try {
@@ -986,11 +1022,13 @@ export async function getBuildIdLogs(request: FastifyRequest<GetBuildIdLogs>) {
where: { buildId, time: { gt: sequence } },
orderBy: { time: 'asc' }
});
const data = await prisma.build.findFirst({ where: { id: buildId } });
const createdAt = day(data.createdAt).utc();
return {
logs,
logs: logs.map(log => {
log.time = Number(log.time)
return log
}),
took: day().diff(createdAt) / 1000,
status: data?.status || 'queued'
}
@@ -1067,3 +1105,58 @@ export async function cancelDeployment(request: FastifyRequest<CancelDeployment>
return errorHandler({ status, message })
}
}
export async function createdBranchDatabase(database: any, baseDatabaseBranch: string, pullmergeRequestId: string) {
try {
if (!baseDatabaseBranch) return
const { id, type, destinationDockerId, rootUser, rootUserPassword, dbUser } = database;
if (destinationDockerId) {
if (type === 'postgresql') {
const decryptedRootUserPassword = decrypt(rootUserPassword);
await executeDockerCmd({
dockerId: destinationDockerId,
command: `docker exec ${id} pg_dump -d "postgresql://postgres:${decryptedRootUserPassword}@${id}:5432/${baseDatabaseBranch}" --encoding=UTF8 --schema-only -f /tmp/${baseDatabaseBranch}.dump`
})
await executeDockerCmd({
dockerId: destinationDockerId,
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "CREATE DATABASE branch_${pullmergeRequestId}"`
})
await executeDockerCmd({
dockerId: destinationDockerId,
command: `docker exec ${id} psql -d "postgresql://postgres:${decryptedRootUserPassword}@${id}:5432/branch_${pullmergeRequestId}" -f /tmp/${baseDatabaseBranch}.dump`
})
await executeDockerCmd({
dockerId: destinationDockerId,
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "ALTER DATABASE branch_${pullmergeRequestId} OWNER TO ${dbUser}"`
})
}
}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function removeBranchDatabase(database: any, pullmergeRequestId: string) {
try {
const { id, type, destinationDockerId, rootUser, rootUserPassword } = database;
if (destinationDockerId) {
if (type === 'postgresql') {
const decryptedRootUserPassword = decrypt(rootUserPassword);
// Terminate all connections to the database
await executeDockerCmd({
dockerId: destinationDockerId,
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = 'branch_${pullmergeRequestId}' AND pid <> pg_backend_pid();"`
})
await executeDockerCmd({
dockerId: destinationDockerId,
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "DROP DATABASE branch_${pullmergeRequestId}"`
})
}
}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}

View File

@@ -1,6 +1,6 @@
import { FastifyPluginAsync } from 'fastify';
import { OnlyId } from '../../../../types';
import { cancelDeployment, checkDNS, checkDomain, checkRepository, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildLogs, getBuildPack, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getSecrets, getStorages, getUsage, listApplications, newApplication, restartApplication, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication, stopPreviewApplication } from './handlers';
import { cancelDeployment, checkDNS, checkDomain, checkRepository, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildLogs, getBuildPack, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getSecrets, getStorages, getUsage, listApplications, newApplication, restartApplication, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveConnectedDatabase, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication, stopPreviewApplication } from './handlers';
import type { CancelDeployment, CheckDNS, CheckDomain, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, GetImages, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, StopPreviewApplication } from './types';
@@ -55,6 +55,8 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.get('/:id/configuration/buildpack', async (request) => await getBuildPack(request));
fastify.post('/:id/configuration/buildpack', async (request, reply) => await saveBuildPack(request, reply));
fastify.post('/:id/configuration/database', async (request, reply) => await saveConnectedDatabase(request, reply));
fastify.get<OnlyId>('/:id/configuration/sshkey', async (request) => await getGitLabSSHKey(request));
fastify.post<OnlyId>('/:id/configuration/sshkey', async (request, reply) => await saveGitLabSSHKey(request, reply));

View File

@@ -20,12 +20,13 @@ export interface SaveApplication extends OnlyId {
denoOptions: string,
baseImage: string,
baseBuildImage: string,
deploymentType: string
deploymentType: string,
baseDatabaseBranch: string
}
}
export interface SaveApplicationSettings extends OnlyId {
Querystring: { domain: string; };
Body: { debug: boolean; previews: boolean; dualCerts: boolean; autodeploy: boolean; branch: string; projectId: number; isBot: boolean; };
Body: { debug: boolean; previews: boolean; dualCerts: boolean; autodeploy: boolean; branch: string; projectId: number; isBot: boolean; isDBBranching: boolean };
}
export interface DeleteApplication extends OnlyId {
Querystring: { domain: string; };

View File

@@ -3,8 +3,7 @@ import cuid from "cuid";
import crypto from "crypto";
import { encrypt, errorHandler, getUIUrl, isDev, prisma } from "../../../lib/common";
import { checkContainer, removeContainer } from "../../../lib/docker";
import { scheduler } from "../../../lib/scheduler";
import { getApplicationFromDBWebhook } from "../../api/v1/applications/handlers";
import { createdBranchDatabase, getApplicationFromDBWebhook, removeBranchDatabase } from "../../api/v1/applications/handlers";
import type { FastifyReply, FastifyRequest } from "fastify";
import type { GitHubEvents, InstallGithub } from "./types";
@@ -67,7 +66,6 @@ export async function configureGitHubApp(request, reply) {
}
export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promise<any> {
try {
const allowedGithubEvents = ['push', 'pull_request'];
const allowedActions = ['opened', 'reopened', 'synchronize', 'closed'];
const githubEvent = request.headers['x-github-event']?.toString().toLowerCase();
@@ -133,8 +131,6 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
where: { id: application.id },
data: { updatedAt: new Date() }
});
console.log(application.id)
await prisma.build.create({
data: {
id: buildId,
@@ -178,6 +174,16 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
where: { id: application.id },
data: { updatedAt: new Date() }
});
if (application.connectedDatabase && pullmergeRequestAction === 'opened' || pullmergeRequestAction === 'reopened') {
// Coolify hosted database
if (application.connectedDatabase.databaseId) {
const databaseId = application.connectedDatabase.databaseId;
const database = await prisma.database.findUnique({ where: { id: databaseId } });
if (database) {
await createdBranchDatabase(database, application.connectedDatabase.hostedDatabaseDBName, pullmergeRequestId);
}
}
}
await prisma.build.create({
data: {
id: buildId,
@@ -197,9 +203,17 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
} else if (pullmergeRequestAction === 'closed') {
if (application.destinationDockerId) {
const id = `${application.id}-${pullmergeRequestId}`;
await removeContainer({ id, dockerId: application.destinationDocker.id });
try {
await removeContainer({ id, dockerId: application.destinationDocker.id });
} catch (error) { }
}
if (application.connectedDatabase.databaseId) {
const databaseId = application.connectedDatabase.databaseId;
const database = await prisma.database.findUnique({ where: { id: databaseId } });
if (database) {
await removeBranchDatabase(database, pullmergeRequestId);
}
}
}
}
}