From f78ea5de071df3539552878addab3a821df9d92f Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 11 Mar 2022 20:47:13 +0100 Subject: [PATCH 01/10] Remove colors Tailwind --- tailwind.config.cjs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tailwind.config.cjs b/tailwind.config.cjs index 8ec0aad53..e5003e5e6 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -1,5 +1,5 @@ const defaultTheme = require('tailwindcss/defaultTheme'); -const colors = require('tailwindcss/colors'); +// const colors = require('tailwindcss/colors'); module.exports = { content: ['./**/*.html', './src/**/*.{js,jsx,ts,tsx,svelte}'], important: true, @@ -18,7 +18,6 @@ module.exports = { sans: ['Poppins', ...defaultTheme.fontFamily.sans] }, colors: { - ...colors, coollabs: '#6B16ED', 'coollabs-100': '#7317FF', coolblack: '#161616', From 48f952c7988f70cb24111a06e4f115309b706a6e Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 11 Mar 2022 20:47:26 +0100 Subject: [PATCH 02/10] fix: Personal Gitlab repos --- .../[id]/configuration/_GitlabRepositories.svelte | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/routes/applications/[id]/configuration/_GitlabRepositories.svelte b/src/routes/applications/[id]/configuration/_GitlabRepositories.svelte index 8fb28161a..3319d2134 100644 --- a/src/routes/applications/[id]/configuration/_GitlabRepositories.svelte +++ b/src/routes/applications/[id]/configuration/_GitlabRepositories.svelte @@ -235,8 +235,11 @@ const url = `/applications/${id}/configuration/repository.json`; try { + const repository = `${selected.group.full_path.replace('-personal', '')}/${ + selected.project.name + }`; await post(url, { - repository: `${selected.group.full_path}/${selected.project.name}`, + repository, branch: selected.branch.name, projectId: selected.project.id, webhookToken From 16ea9a3e07bc6e93a310a8e827ad14fce1666cda Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 11 Mar 2022 20:52:11 +0100 Subject: [PATCH 03/10] Update options request --- src/routes/webhooks/github/events.ts | 2 +- src/routes/webhooks/gitlab/events.ts | 2 +- src/routes/webhooks/gitlab/index.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routes/webhooks/github/events.ts b/src/routes/webhooks/github/events.ts index 40a60e29b..b50c19929 100644 --- a/src/routes/webhooks/github/events.ts +++ b/src/routes/webhooks/github/events.ts @@ -9,7 +9,7 @@ import { dev } from '$app/env'; export const options: RequestHandler = async () => { return { - status: 200, + status: 204, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', diff --git a/src/routes/webhooks/gitlab/events.ts b/src/routes/webhooks/gitlab/events.ts index 89aec7b4d..1c125ec02 100644 --- a/src/routes/webhooks/gitlab/events.ts +++ b/src/routes/webhooks/gitlab/events.ts @@ -9,7 +9,7 @@ import { dev } from '$app/env'; export const options: RequestHandler = async () => { return { - status: 200, + status: 204, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', diff --git a/src/routes/webhooks/gitlab/index.ts b/src/routes/webhooks/gitlab/index.ts index f3d930ace..0cc4f7ab8 100644 --- a/src/routes/webhooks/gitlab/index.ts +++ b/src/routes/webhooks/gitlab/index.ts @@ -7,7 +7,7 @@ import cookie from 'cookie'; export const options: RequestHandler = async () => { return { - status: 200, + status: 204, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', From c5c9f845030efd24299b28fd3e61b67a5a30de6e Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 11 Mar 2022 21:18:12 +0100 Subject: [PATCH 04/10] feat: Webhooks inititate all applications with the correct branch --- src/lib/common.ts | 1 + src/lib/database/applications.ts | 67 ++--- src/lib/queues/index.ts | 2 +- .../configuration/_GithubRepositories.svelte | 2 +- .../configuration/_GitlabRepositories.svelte | 2 +- src/routes/webhooks/github/events.ts | 233 +++++++++--------- src/routes/webhooks/gitlab/events.ts | 180 +++++++------- 7 files changed, 256 insertions(+), 231 deletions(-) diff --git a/src/lib/common.ts b/src/lib/common.ts index c36196113..db6e55e51 100644 --- a/src/lib/common.ts +++ b/src/lib/common.ts @@ -11,6 +11,7 @@ import { version as currentVersion } from '../../package.json'; import dayjs from 'dayjs'; import Cookie from 'cookie'; import os from 'os'; +import cuid from 'cuid'; try { if (!dev) { diff --git a/src/lib/database/applications.ts b/src/lib/database/applications.ts index 64cc1444e..43bacd46a 100644 --- a/src/lib/database/applications.ts +++ b/src/lib/database/applications.ts @@ -58,15 +58,6 @@ export async function removeApplication({ id, teamId }) { const id = containerObj.ID; const preview = containerObj.Image.split('-')[1]; await removeDestinationDocker({ id, engine: destinationDocker.engine }); - try { - if (preview) { - await removeProxyConfiguration({ domain: `${preview}.${domain}` }); - } else { - await removeProxyConfiguration({ domain }); - } - } catch (error) { - console.log(error); - } } } } @@ -79,7 +70,7 @@ export async function removeApplication({ id, teamId }) { export async function getApplicationWebhook({ projectId, branch }) { try { - let body = await prisma.application.findFirst({ + let applications = await prisma.application.findMany({ where: { projectId, branch }, include: { destinationDocker: true, @@ -88,30 +79,40 @@ export async function getApplicationWebhook({ projectId, branch }) { secrets: true } }); - - if (body.gitSource?.githubApp?.clientSecret) { - body.gitSource.githubApp.clientSecret = decrypt(body.gitSource.githubApp.clientSecret); + for (const application of applications) { + if (application.gitSource?.githubApp?.clientSecret) { + application.gitSource.githubApp.clientSecret = decrypt( + application.gitSource.githubApp.clientSecret + ); + } + if (application.gitSource?.githubApp?.webhookSecret) { + application.gitSource.githubApp.webhookSecret = decrypt( + application.gitSource.githubApp.webhookSecret + ); + } + if (application.gitSource?.githubApp?.privateKey) { + application.gitSource.githubApp.privateKey = decrypt( + application.gitSource.githubApp.privateKey + ); + } + if (application?.gitSource?.gitlabApp?.appSecret) { + application.gitSource.gitlabApp.appSecret = decrypt( + application.gitSource.gitlabApp.appSecret + ); + } + if (application?.gitSource?.gitlabApp?.webhookToken) { + application.gitSource.gitlabApp.webhookToken = decrypt( + application.gitSource.gitlabApp.webhookToken + ); + } + if (application?.secrets.length > 0) { + application.secrets = application.secrets.map((s) => { + s.value = decrypt(s.value); + return s; + }); + } } - if (body.gitSource?.githubApp?.webhookSecret) { - body.gitSource.githubApp.webhookSecret = decrypt(body.gitSource.githubApp.webhookSecret); - } - if (body.gitSource?.githubApp?.privateKey) { - body.gitSource.githubApp.privateKey = decrypt(body.gitSource.githubApp.privateKey); - } - if (body?.gitSource?.gitlabApp?.appSecret) { - body.gitSource.gitlabApp.appSecret = decrypt(body.gitSource.gitlabApp.appSecret); - } - if (body?.gitSource?.gitlabApp?.webhookToken) { - body.gitSource.gitlabApp.webhookToken = decrypt(body.gitSource.gitlabApp.webhookToken); - } - if (body?.secrets.length > 0) { - body.secrets = body.secrets.map((s) => { - s.value = decrypt(s.value); - return s; - }); - } - - return { ...body }; + return [...applications]; } catch (e) { throw { status: 404, body: { message: e.message } }; } diff --git a/src/lib/queues/index.ts b/src/lib/queues/index.ts index 9b93c6c0c..d26126d59 100644 --- a/src/lib/queues/index.ts +++ b/src/lib/queues/index.ts @@ -120,7 +120,7 @@ buildWorker.on('completed', async (job: Bullmq.Job) => { } catch (err) { console.log(err); } finally { - const workdir = `/tmp/build-sources/${job.data.repository}/`; + const workdir = `/tmp/build-sources/${job.data.repository}/${job.data.build_id}`; await asyncExecShell(`rm -fr ${workdir}`); } return; diff --git a/src/routes/applications/[id]/configuration/_GithubRepositories.svelte b/src/routes/applications/[id]/configuration/_GithubRepositories.svelte index 960d19d10..776f719a3 100644 --- a/src/routes/applications/[id]/configuration/_GithubRepositories.svelte +++ b/src/routes/applications/[id]/configuration/_GithubRepositories.svelte @@ -172,7 +172,7 @@ class="w-96" disabled={!selected.repository} bind:value={selected.branch} - on:change={isBranchAlreadyUsed} + on:change={() => (showSave = true)} > {#if !selected.repository} diff --git a/src/routes/applications/[id]/configuration/_GitlabRepositories.svelte b/src/routes/applications/[id]/configuration/_GitlabRepositories.svelte index 3319d2134..737d08c04 100644 --- a/src/routes/applications/[id]/configuration/_GitlabRepositories.svelte +++ b/src/routes/applications/[id]/configuration/_GitlabRepositories.svelte @@ -305,7 +305,7 @@ name="branch" class="w-96" bind:value={selected.branch} - on:change={isBranchAlreadyUsed} + on:change={() => (showSave = true)} disabled={!selected.project} > diff --git a/src/routes/webhooks/github/events.ts b/src/routes/webhooks/github/events.ts index b50c19929..1d2ad03b2 100644 --- a/src/routes/webhooks/github/events.ts +++ b/src/routes/webhooks/github/events.ts @@ -20,7 +20,6 @@ export const options: RequestHandler = async () => { export const post: RequestHandler = async (event) => { try { - const buildId = cuid(); const allowedGithubEvents = ['push', 'pull_request']; const allowedActions = ['opened', 'reopened', 'synchronize', 'closed']; const githubEvent = event.request.headers.get('x-github-event')?.toLowerCase(); @@ -45,137 +44,147 @@ export const post: RequestHandler = async (event) => { branch = body.pull_request.head.ref.split('/')[2]; } - const applicationFound = await db.getApplicationWebhook({ projectId, branch }); - if (applicationFound) { - const webhookSecret = applicationFound.gitSource.githubApp.webhookSecret; - const hmac = crypto.createHmac('sha256', webhookSecret); - const digest = Buffer.from( - 'sha256=' + hmac.update(JSON.stringify(body)).digest('hex'), - 'utf8' - ); - const checksum = Buffer.from(githubSignature, 'utf8'); - if (!dev) { - if (checksum.length !== digest.length || !crypto.timingSafeEqual(digest, checksum)) { - return { - status: 500, - body: { - message: 'SHA256 checksum failed. Are you doing something fishy?' - } - }; - } - } + const applications = await db.getApplicationWebhook({ projectId, branch }); + if (applications.length > 0) { + for (const application of applications) { + const buildId = cuid(); - if (githubEvent === 'push') { - if (!applicationFound.configHash) { - const configHash = crypto - .createHash('sha256') - .update( - JSON.stringify({ - buildPack: applicationFound.buildPack, - port: applicationFound.port, - installCommand: applicationFound.installCommand, - buildCommand: applicationFound.buildCommand, - startCommand: applicationFound.startCommand - }) - ) - .digest('hex'); - await db.prisma.application.updateMany({ - where: { branch, projectId }, - data: { configHash } - }); - } - await db.prisma.application.update({ - where: { id: applicationFound.id }, - data: { updatedAt: new Date() } - }); - await buildQueue.add(buildId, { - build_id: buildId, - type: 'webhook_commit', - ...applicationFound - }); - return { - status: 200, - body: { - message: 'Queued. Thank you!' + const webhookSecret = application.gitSource.githubApp.webhookSecret; + const hmac = crypto.createHmac('sha256', webhookSecret); + const digest = Buffer.from( + 'sha256=' + hmac.update(JSON.stringify(body)).digest('hex'), + 'utf8' + ); + const checksum = Buffer.from(githubSignature, 'utf8'); + if (!dev) { + if (checksum.length !== digest.length || !crypto.timingSafeEqual(digest, checksum)) { + return { + status: 500, + body: { + message: 'SHA256 checksum failed. Are you doing something fishy?' + } + }; } - }; - } else if (githubEvent === 'pull_request') { - const pullmergeRequestId = body.number; - const pullmergeRequestAction = body.action; - const sourceBranch = body.pull_request.head.ref; - if (!allowedActions.includes(pullmergeRequestAction)) { - return { - status: 500, - body: { - message: 'Action not allowed.' - } - }; } - if (applicationFound.settings.previews) { - if (applicationFound.destinationDockerId) { - const isRunning = await checkContainer( - applicationFound.destinationDocker.engine, - applicationFound.id - ); - if (!isRunning) { + if (githubEvent === 'push') { + if (!application.configHash) { + const configHash = crypto + .createHash('sha256') + .update( + JSON.stringify({ + buildPack: application.buildPack, + port: application.port, + installCommand: application.installCommand, + buildCommand: application.buildCommand, + startCommand: application.startCommand + }) + ) + .digest('hex'); + await db.prisma.application.updateMany({ + where: { branch, projectId }, + data: { configHash } + }); + } + await db.prisma.application.update({ + where: { id: application.id }, + data: { updatedAt: new Date() } + }); + await buildQueue.add(buildId, { + build_id: buildId, + type: 'webhook_commit', + ...application + }); + return { + status: 200, + body: { + message: 'Queued. Thank you!' + } + }; + } else if (githubEvent === 'pull_request') { + const pullmergeRequestId = body.number; + const pullmergeRequestAction = body.action; + const sourceBranch = body.pull_request.head.ref; + if (!allowedActions.includes(pullmergeRequestAction)) { + return { + status: 500, + body: { + message: 'Action not allowed.' + } + }; + } + + if (application.settings.previews) { + if (application.destinationDockerId) { + const isRunning = await checkContainer( + application.destinationDocker.engine, + application.id + ); + if (!isRunning) { + return { + status: 500, + body: { + message: 'Application not running.' + } + }; + } + } + if ( + pullmergeRequestAction === 'opened' || + pullmergeRequestAction === 'reopened' || + pullmergeRequestAction === 'synchronize' + ) { + await db.prisma.application.update({ + where: { id: application.id }, + data: { updatedAt: new Date() } + }); + await buildQueue.add(buildId, { + build_id: buildId, + type: 'webhook_pr', + ...application, + sourceBranch, + pullmergeRequestId + }); return { - status: 500, + status: 200, body: { - message: 'Application not running.' + message: 'Queued. Thank you!' + } + }; + } else if (pullmergeRequestAction === 'closed') { + if (application.destinationDockerId) { + const id = `${application.id}-${pullmergeRequestId}`; + const engine = application.destinationDocker.engine; + await removeDestinationDocker({ id, engine }); + } + return { + status: 200, + body: { + message: 'Removed preview. Thank you!' } }; } - } - if ( - pullmergeRequestAction === 'opened' || - pullmergeRequestAction === 'reopened' || - pullmergeRequestAction === 'synchronize' - ) { - await db.prisma.application.update({ - where: { id: applicationFound.id }, - data: { updatedAt: new Date() } - }); - await buildQueue.add(buildId, { - build_id: buildId, - type: 'webhook_pr', - ...applicationFound, - sourceBranch, - pullmergeRequestId - }); + } else { return { - status: 200, + status: 500, body: { - message: 'Queued. Thank you!' - } - }; - } else if (pullmergeRequestAction === 'closed') { - if (applicationFound.destinationDockerId) { - const id = `${applicationFound.id}-${pullmergeRequestId}`; - const engine = applicationFound.destinationDocker.engine; - await removeDestinationDocker({ id, engine }); - } - return { - status: 200, - body: { - message: 'Removed preview. Thank you!' + message: 'Pull request previews are not enabled.' } }; } - } else { - return { - status: 500, - body: { - message: 'Pull request previews are not enabled.' - } - }; } } + return { + status: 500, + body: { + message: 'Not handled event.' + } + }; } return { status: 500, body: { - message: 'Not handled event.' + message: 'No applications configured in Coolify.' } }; } catch (err) { diff --git a/src/routes/webhooks/gitlab/events.ts b/src/routes/webhooks/gitlab/events.ts index 1c125ec02..030815fdd 100644 --- a/src/routes/webhooks/gitlab/events.ts +++ b/src/routes/webhooks/gitlab/events.ts @@ -21,42 +21,46 @@ export const options: RequestHandler = async () => { export const post: RequestHandler = async (event) => { const allowedActions = ['opened', 'reopen', 'close', 'open', 'update']; const body = await event.request.json(); - const buildId = cuid(); + try { const { object_kind: objectKind } = body; if (objectKind === 'push') { const { ref } = body; const projectId = Number(body['project_id']); const branch = ref.split('/')[2]; - const applicationFound = await db.getApplicationWebhook({ projectId, branch }); - if (applicationFound) { - if (!applicationFound.configHash) { - const configHash = crypto - .createHash('sha256') - .update( - JSON.stringify({ - buildPack: applicationFound.buildPack, - port: applicationFound.port, - installCommand: applicationFound.installCommand, - buildCommand: applicationFound.buildCommand, - startCommand: applicationFound.startCommand - }) - ) - .digest('hex'); - await db.prisma.application.updateMany({ - where: { branch, projectId }, - data: { configHash } + const applications = await db.getApplicationWebhook({ projectId, branch }); + if (applications.length > 0) { + for (const application of applications) { + const buildId = cuid(); + if (!application.configHash) { + const configHash = crypto + .createHash('sha256') + .update( + JSON.stringify({ + buildPack: application.buildPack, + port: application.port, + installCommand: application.installCommand, + buildCommand: application.buildCommand, + startCommand: application.startCommand + }) + ) + .digest('hex'); + await db.prisma.application.updateMany({ + where: { branch, projectId }, + data: { configHash } + }); + } + await db.prisma.application.update({ + where: { id: application.id }, + data: { updatedAt: new Date() } + }); + await buildQueue.add(buildId, { + build_id: buildId, + type: 'webhook_commit', + ...application }); } - await db.prisma.application.update({ - where: { id: applicationFound.id }, - data: { updatedAt: new Date() } - }); - await buildQueue.add(buildId, { - build_id: buildId, - type: 'webhook_commit', - ...applicationFound - }); + return { status: 200, body: { @@ -64,6 +68,12 @@ export const post: RequestHandler = async (event) => { } }; } + return { + status: 500, + body: { + message: 'No applications configured in Coolify.' + } + }; } else if (objectKind === 'merge_request') { const webhookToken = event.request.headers.get('x-gitlab-token'); if (!webhookToken) { @@ -98,69 +108,73 @@ export const post: RequestHandler = async (event) => { }; } - const applicationFound = await db.getApplicationWebhook({ projectId, branch: targetBranch }); - if (applicationFound) { - if (applicationFound.settings.previews) { - if (applicationFound.destinationDockerId) { - const isRunning = await checkContainer( - applicationFound.destinationDocker.engine, - applicationFound.id - ); - if (!isRunning) { + const applications = await db.getApplicationWebhook({ projectId, branch: targetBranch }); + if (applications.length > 0) { + for (const application of applications) { + const buildId = cuid(); + if (application.settings.previews) { + if (application.destinationDockerId) { + const isRunning = await checkContainer( + application.destinationDocker.engine, + application.id + ); + if (!isRunning) { + return { + status: 500, + body: { + message: 'Application not running.' + } + }; + } + } + if (!dev && application.gitSource.gitlabApp.webhookToken !== webhookToken) { return { status: 500, body: { - message: 'Application not running.' + message: 'Ooops, something is not okay, are you okay?' + } + }; + } + if ( + action === 'opened' || + action === 'reopen' || + action === 'open' || + action === 'update' + ) { + await db.prisma.application.update({ + where: { id: application.id }, + data: { updatedAt: new Date() } + }); + await buildQueue.add(buildId, { + build_id: buildId, + type: 'webhook_mr', + ...application, + sourceBranch, + pullmergeRequestId + }); + return { + status: 200, + body: { + message: 'Queued. Thank you!' + } + }; + } else if (action === 'close') { + if (application.destinationDockerId) { + const id = `${application.id}-${pullmergeRequestId}`; + const engine = application.destinationDocker.engine; + await removeDestinationDocker({ id, engine }); + } + + return { + status: 200, + body: { + message: 'Removed preview. Thank you!' } }; } } - if (!dev && applicationFound.gitSource.gitlabApp.webhookToken !== webhookToken) { - return { - status: 500, - body: { - message: 'Ooops, something is not okay, are you okay?' - } - }; - } - if ( - action === 'opened' || - action === 'reopen' || - action === 'open' || - action === 'update' - ) { - await db.prisma.application.update({ - where: { id: applicationFound.id }, - data: { updatedAt: new Date() } - }); - await buildQueue.add(buildId, { - build_id: buildId, - type: 'webhook_mr', - ...applicationFound, - sourceBranch, - pullmergeRequestId - }); - return { - status: 200, - body: { - message: 'Queued. Thank you!' - } - }; - } else if (action === 'close') { - if (applicationFound.destinationDockerId) { - const id = `${applicationFound.id}-${pullmergeRequestId}`; - const engine = applicationFound.destinationDocker.engine; - await removeDestinationDocker({ id, engine }); - } - - return { - status: 200, - body: { - message: 'Removed preview. Thank you!' - } - }; - } } + return { status: 500, body: { From 3de2ea1523cbc4403384679fc7525c775b9bec81 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 11 Mar 2022 21:19:03 +0100 Subject: [PATCH 05/10] chore: version++ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bb42f2869..04dbc7e7d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "coolify", "description": "An open-source & self-hostable Heroku / Netlify alternative.", - "version": "2.0.28", + "version": "2.0.29", "license": "AGPL-3.0", "scripts": { "dev": "docker-compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0", From 7c273a3a487e1ca01d4f7bd3abda34429c720b96 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 11 Mar 2022 21:28:27 +0100 Subject: [PATCH 06/10] feat: Check ssl for new apps/services first --- src/lib/letsencrypt/index.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/letsencrypt/index.ts b/src/lib/letsencrypt/index.ts index 61a8c0c37..ff189845f 100644 --- a/src/lib/letsencrypt/index.ts +++ b/src/lib/letsencrypt/index.ts @@ -99,7 +99,8 @@ export async function letsEncrypt(domain, id = null, isCoolify = false) { export async function generateSSLCerts() { const ssls = []; const applications = await db.prisma.application.findMany({ - include: { destinationDocker: true, settings: true } + include: { destinationDocker: true, settings: true }, + orderBy: { createdAt: 'desc' } }); for (const application of applications) { const { @@ -139,7 +140,8 @@ export async function generateSSLCerts() { plausibleAnalytics: true, vscodeserver: true, wordpress: true - } + }, + orderBy: { createdAt: 'desc' } }); for (const service of services) { From fa6cf068c73744cec55fd3008590b5f45891e75a Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 11 Mar 2022 22:36:21 +0100 Subject: [PATCH 07/10] feat: Autodeploy pause --- .../20220311213422_autodeploy/migration.sql | 19 ++++++++ prisma/schema.prisma | 1 + src/lib/database/applications.ts | 37 ++++++++++++---- .../configuration/_GithubRepositories.svelte | 13 +++++- .../configuration/_GitlabRepositories.svelte | 13 +++++- .../[id]/configuration/repository.json.ts | 11 ++++- src/routes/applications/[id]/index.svelte | 44 ++++++++++++++----- src/routes/applications/[id]/settings.json.ts | 11 ++++- src/routes/webhooks/github/events.ts | 4 +- src/routes/webhooks/gitlab/events.ts | 4 +- 10 files changed, 127 insertions(+), 30 deletions(-) create mode 100644 prisma/migrations/20220311213422_autodeploy/migration.sql diff --git a/prisma/migrations/20220311213422_autodeploy/migration.sql b/prisma/migrations/20220311213422_autodeploy/migration.sql new file mode 100644 index 000000000..d534d9372 --- /dev/null +++ b/prisma/migrations/20220311213422_autodeploy/migration.sql @@ -0,0 +1,19 @@ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_ApplicationSettings" ( + "id" TEXT NOT NULL PRIMARY KEY, + "applicationId" TEXT NOT NULL, + "dualCerts" BOOLEAN NOT NULL DEFAULT false, + "debug" BOOLEAN NOT NULL DEFAULT false, + "previews" BOOLEAN NOT NULL DEFAULT false, + "autodeploy" BOOLEAN NOT NULL DEFAULT true, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_ApplicationSettings" ("applicationId", "createdAt", "debug", "dualCerts", "id", "previews", "updatedAt") SELECT "applicationId", "createdAt", "debug", "dualCerts", "id", "previews", "updatedAt" FROM "ApplicationSettings"; +DROP TABLE "ApplicationSettings"; +ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings"; +CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId"); +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8c4fdab53..85b78fdc4 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -104,6 +104,7 @@ model ApplicationSettings { dualCerts Boolean @default(false) debug Boolean @default(false) previews Boolean @default(false) + autodeploy Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } diff --git a/src/lib/database/applications.ts b/src/lib/database/applications.ts index 43bacd46a..60d58f27f 100644 --- a/src/lib/database/applications.ts +++ b/src/lib/database/applications.ts @@ -71,7 +71,7 @@ export async function removeApplication({ id, teamId }) { export async function getApplicationWebhook({ projectId, branch }) { try { let applications = await prisma.application.findMany({ - where: { projectId, branch }, + where: { projectId, branch, settings: { autodeploy: true } }, include: { destinationDocker: true, settings: true, @@ -158,24 +158,41 @@ export async function getApplication({ id, teamId }) { return { ...body }; } -export async function configureGitRepository({ id, repository, branch, projectId, webhookToken }) { +export async function configureGitRepository({ + id, + repository, + branch, + projectId, + webhookToken, + autodeploy +}) { if (webhookToken) { const encryptedWebhookToken = encrypt(webhookToken); - return await prisma.application.update({ + await prisma.application.update({ where: { id }, data: { repository, branch, projectId, - gitSource: { update: { gitlabApp: { update: { webhookToken: encryptedWebhookToken } } } } + gitSource: { update: { gitlabApp: { update: { webhookToken: encryptedWebhookToken } } } }, + settings: { update: { autodeploy } } } }); } else { - return await prisma.application.update({ + await prisma.application.update({ where: { id }, - data: { repository, branch, projectId } + data: { repository, branch, projectId, settings: { update: { autodeploy } } } }); } + if (!autodeploy) { + const applications = await prisma.application.findMany({ where: { branch, projectId } }); + for (const application of applications) { + await prisma.applicationSettings.updateMany({ + where: { applicationId: application.id }, + data: { autodeploy: false } + }); + } + } } export async function configureBuildPack({ id, buildPack }) { @@ -210,10 +227,14 @@ export async function configureApplication({ }); } -export async function setApplicationSettings({ id, debug, previews, dualCerts }) { +export async function checkDoubleBranch(branch, projectId) { + const applications = await prisma.application.findMany({ where: { branch, projectId } }); + return applications.length > 1; +} +export async function setApplicationSettings({ id, debug, previews, dualCerts, autodeploy }) { return await prisma.application.update({ where: { id }, - data: { settings: { update: { debug, previews, dualCerts } } }, + data: { settings: { update: { debug, previews, dualCerts, autodeploy } } }, include: { destinationDocker: true } }); } diff --git a/src/routes/applications/[id]/configuration/_GithubRepositories.svelte b/src/routes/applications/[id]/configuration/_GithubRepositories.svelte index 776f719a3..9ba619d64 100644 --- a/src/routes/applications/[id]/configuration/_GithubRepositories.svelte +++ b/src/routes/applications/[id]/configuration/_GithubRepositories.svelte @@ -28,6 +28,8 @@ branch: undefined }; let showSave = false; + let autodeploy = application.settings.autodeploy || true; + async function loadRepositoriesByPage(page = 0) { return await get(`${apiUrl}/installation/repositories?per_page=100&page=${page}`, { Authorization: `token ${$gitTokens.githubToken}` @@ -69,7 +71,14 @@ `/applications/${id}/configuration/repository.json?repository=${selected.repository}&branch=${selected.branch}` ); if (data.used) { - errorNotification('This branch is already used by another application.'); + const sure = confirm( + `This branch is already used by another application. Webhooks won't work in this case for both applications. Are you sure you want to use it?` + ); + if (sure) { + autodeploy = false; + showSave = true; + return true; + } showSave = false; return true; } @@ -172,7 +181,7 @@ class="w-96" disabled={!selected.repository} bind:value={selected.branch} - on:change={() => (showSave = true)} + on:change={isBranchAlreadyUsed} > {#if !selected.repository} diff --git a/src/routes/applications/[id]/configuration/_GitlabRepositories.svelte b/src/routes/applications/[id]/configuration/_GitlabRepositories.svelte index 737d08c04..e3a26a38d 100644 --- a/src/routes/applications/[id]/configuration/_GitlabRepositories.svelte +++ b/src/routes/applications/[id]/configuration/_GitlabRepositories.svelte @@ -30,6 +30,7 @@ let projects = []; let branches = []; let showSave = false; + let autodeploy = application.settings.autodeploy || true; let selected = { group: undefined, @@ -138,7 +139,14 @@ `/applications/${id}/configuration/repository.json?repository=${selected.project.path_with_namespace}&branch=${selected.branch.name}` ); if (data.used) { - errorNotification('This branch is already used by another application.'); + const sure = confirm( + `This branch is already used by another application. Webhooks won't work in this case for both applications. Are you sure you want to use it?` + ); + if (sure) { + autodeploy = false; + showSave = true; + return true; + } showSave = false; return true; } @@ -242,6 +250,7 @@ repository, branch: selected.branch.name, projectId: selected.project.id, + autodeploy, webhookToken }); return await goto(from || `/applications/${id}/configuration/buildpack`); @@ -305,7 +314,7 @@ name="branch" class="w-96" bind:value={selected.branch} - on:change={() => (showSave = true)} + on:change={isBranchAlreadyUsed} disabled={!selected.project} > diff --git a/src/routes/applications/[id]/configuration/repository.json.ts b/src/routes/applications/[id]/configuration/repository.json.ts index 6d133f29f..38987c4c6 100644 --- a/src/routes/applications/[id]/configuration/repository.json.ts +++ b/src/routes/applications/[id]/configuration/repository.json.ts @@ -30,14 +30,21 @@ export const post: RequestHandler = async (event) => { if (status === 401) return { status, body }; const { id } = event.params; - let { repository, branch, projectId, webhookToken } = await event.request.json(); + let { repository, branch, projectId, webhookToken, autodeploy } = await event.request.json(); repository = repository.toLowerCase(); branch = branch.toLowerCase(); projectId = Number(projectId); try { - await db.configureGitRepository({ id, repository, branch, projectId, webhookToken }); + await db.configureGitRepository({ + id, + repository, + branch, + projectId, + webhookToken, + autodeploy + }); return { status: 201 }; } catch (error) { return ErrorHandler(error); diff --git a/src/routes/applications/[id]/index.svelte b/src/routes/applications/[id]/index.svelte index 35af22c4d..a0ccec921 100644 --- a/src/routes/applications/[id]/index.svelte +++ b/src/routes/applications/[id]/index.svelte @@ -56,6 +56,7 @@ let debug = application.settings.debug; let previews = application.settings.previews; let dualCerts = application.settings.dualCerts; + let autodeploy = application.settings.autodeploy; if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) { application.fqdn = `http://${cuid()}.demo.coolify.io`; @@ -75,10 +76,32 @@ if (name === 'dualCerts') { dualCerts = !dualCerts; } + if (name === 'autodeploy') { + autodeploy = !autodeploy; + } try { - await post(`/applications/${id}/settings.json`, { previews, debug, dualCerts }); + await post(`/applications/${id}/settings.json`, { + previews, + debug, + dualCerts, + autodeploy, + branch: application.branch, + projectId: application.projectId + }); return toast.push('Settings saved.'); } catch ({ error }) { + if (name === 'debug') { + debug = !debug; + } + if (name === 'previews') { + previews = !previews; + } + if (name === 'dualCerts') { + dualCerts = !dualCerts; + } + if (name === 'autodeploy') { + autodeploy = !autodeploy; + } return errorNotification(error); } } @@ -383,22 +406,23 @@
Features
-
+
+ changeSettings('autodeploy')} + title="Enable Automatic Deployment" + description="Enable automatic deployment through webhooks." + /> +
changeSettings('previews')} title="Enable MR/PR Previews" - description="Creates previews from pull and merge requests." + description="Enable preview deployments from pull or merge requests." />
diff --git a/src/routes/applications/[id]/settings.json.ts b/src/routes/applications/[id]/settings.json.ts index 6b0b3f808..aea78ff96 100644 --- a/src/routes/applications/[id]/settings.json.ts +++ b/src/routes/applications/[id]/settings.json.ts @@ -8,10 +8,17 @@ export const post: RequestHandler = async (event) => { if (status === 401) return { status, body }; const { id } = event.params; - const { debug, previews, dualCerts } = await event.request.json(); + const { debug, previews, dualCerts, autodeploy, branch, projectId } = await event.request.json(); try { - await db.setApplicationSettings({ id, debug, previews, dualCerts }); + const isDouble = await db.checkDoubleBranch(branch, projectId); + if (isDouble && autodeploy) { + throw { + message: + 'Cannot activate automatic deployments until only one application is defined for this repository / branch.' + }; + } + await db.setApplicationSettings({ id, debug, previews, dualCerts, autodeploy }); return { status: 201 }; } catch (error) { return ErrorHandler(error); diff --git a/src/routes/webhooks/github/events.ts b/src/routes/webhooks/github/events.ts index 1d2ad03b2..ee2ced11d 100644 --- a/src/routes/webhooks/github/events.ts +++ b/src/routes/webhooks/github/events.ts @@ -81,8 +81,8 @@ export const post: RequestHandler = async (event) => { }) ) .digest('hex'); - await db.prisma.application.updateMany({ - where: { branch, projectId }, + await db.prisma.application.update({ + where: { id: application.id }, data: { configHash } }); } diff --git a/src/routes/webhooks/gitlab/events.ts b/src/routes/webhooks/gitlab/events.ts index 030815fdd..8b35d1b41 100644 --- a/src/routes/webhooks/gitlab/events.ts +++ b/src/routes/webhooks/gitlab/events.ts @@ -45,8 +45,8 @@ export const post: RequestHandler = async (event) => { }) ) .digest('hex'); - await db.prisma.application.updateMany({ - where: { branch, projectId }, + await db.prisma.application.update({ + where: { id: application.id }, data: { configHash } }); } From c6b4d04e26905ae7ce401fa34b6d646f19f63c5c Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 11 Mar 2022 22:48:55 +0100 Subject: [PATCH 08/10] Revert double build --- src/lib/database/applications.ts | 66 +++-- src/routes/applications/[id]/__layout.svelte | 4 +- src/routes/webhooks/github/events.ts | 251 +++++++++---------- src/routes/webhooks/gitlab/events.ts | 180 ++++++------- 4 files changed, 237 insertions(+), 264 deletions(-) diff --git a/src/lib/database/applications.ts b/src/lib/database/applications.ts index 60d58f27f..ecbb22484 100644 --- a/src/lib/database/applications.ts +++ b/src/lib/database/applications.ts @@ -70,7 +70,7 @@ export async function removeApplication({ id, teamId }) { export async function getApplicationWebhook({ projectId, branch }) { try { - let applications = await prisma.application.findMany({ + let application = await prisma.application.findFirst({ where: { projectId, branch, settings: { autodeploy: true } }, include: { destinationDocker: true, @@ -79,40 +79,38 @@ export async function getApplicationWebhook({ projectId, branch }) { secrets: true } }); - for (const application of applications) { - if (application.gitSource?.githubApp?.clientSecret) { - application.gitSource.githubApp.clientSecret = decrypt( - application.gitSource.githubApp.clientSecret - ); - } - if (application.gitSource?.githubApp?.webhookSecret) { - application.gitSource.githubApp.webhookSecret = decrypt( - application.gitSource.githubApp.webhookSecret - ); - } - if (application.gitSource?.githubApp?.privateKey) { - application.gitSource.githubApp.privateKey = decrypt( - application.gitSource.githubApp.privateKey - ); - } - if (application?.gitSource?.gitlabApp?.appSecret) { - application.gitSource.gitlabApp.appSecret = decrypt( - application.gitSource.gitlabApp.appSecret - ); - } - if (application?.gitSource?.gitlabApp?.webhookToken) { - application.gitSource.gitlabApp.webhookToken = decrypt( - application.gitSource.gitlabApp.webhookToken - ); - } - if (application?.secrets.length > 0) { - application.secrets = application.secrets.map((s) => { - s.value = decrypt(s.value); - return s; - }); - } + if (application.gitSource?.githubApp?.clientSecret) { + application.gitSource.githubApp.clientSecret = decrypt( + application.gitSource.githubApp.clientSecret + ); } - return [...applications]; + if (application.gitSource?.githubApp?.webhookSecret) { + application.gitSource.githubApp.webhookSecret = decrypt( + application.gitSource.githubApp.webhookSecret + ); + } + if (application.gitSource?.githubApp?.privateKey) { + application.gitSource.githubApp.privateKey = decrypt( + application.gitSource.githubApp.privateKey + ); + } + if (application?.gitSource?.gitlabApp?.appSecret) { + application.gitSource.gitlabApp.appSecret = decrypt( + application.gitSource.gitlabApp.appSecret + ); + } + if (application?.gitSource?.gitlabApp?.webhookToken) { + application.gitSource.gitlabApp.webhookToken = decrypt( + application.gitSource.gitlabApp.webhookToken + ); + } + if (application?.secrets.length > 0) { + application.secrets = application.secrets.map((s) => { + s.value = decrypt(s.value); + return s; + }); + } + return { ...application }; } catch (e) { throw { status: 404, body: { message: e.message } }; } diff --git a/src/routes/applications/[id]/__layout.svelte b/src/routes/applications/[id]/__layout.svelte index 2c979a4ab..041be4a82 100644 --- a/src/routes/applications/[id]/__layout.svelte +++ b/src/routes/applications/[id]/__layout.svelte @@ -108,11 +108,9 @@ try { loading = true; await post(`/applications/${id}/stop.json`, {}); - isRunning = false; + return window.location.reload(); } catch ({ error }) { return errorNotification(error); - } finally { - loading = false; } } diff --git a/src/routes/webhooks/github/events.ts b/src/routes/webhooks/github/events.ts index ee2ced11d..b50c19929 100644 --- a/src/routes/webhooks/github/events.ts +++ b/src/routes/webhooks/github/events.ts @@ -20,6 +20,7 @@ export const options: RequestHandler = async () => { export const post: RequestHandler = async (event) => { try { + const buildId = cuid(); const allowedGithubEvents = ['push', 'pull_request']; const allowedActions = ['opened', 'reopened', 'synchronize', 'closed']; const githubEvent = event.request.headers.get('x-github-event')?.toLowerCase(); @@ -44,147 +45,137 @@ export const post: RequestHandler = async (event) => { branch = body.pull_request.head.ref.split('/')[2]; } - const applications = await db.getApplicationWebhook({ projectId, branch }); - if (applications.length > 0) { - for (const application of applications) { - const buildId = cuid(); - - const webhookSecret = application.gitSource.githubApp.webhookSecret; - const hmac = crypto.createHmac('sha256', webhookSecret); - const digest = Buffer.from( - 'sha256=' + hmac.update(JSON.stringify(body)).digest('hex'), - 'utf8' - ); - const checksum = Buffer.from(githubSignature, 'utf8'); - if (!dev) { - if (checksum.length !== digest.length || !crypto.timingSafeEqual(digest, checksum)) { - return { - status: 500, - body: { - message: 'SHA256 checksum failed. Are you doing something fishy?' - } - }; - } - } - - if (githubEvent === 'push') { - if (!application.configHash) { - const configHash = crypto - .createHash('sha256') - .update( - JSON.stringify({ - buildPack: application.buildPack, - port: application.port, - installCommand: application.installCommand, - buildCommand: application.buildCommand, - startCommand: application.startCommand - }) - ) - .digest('hex'); - await db.prisma.application.update({ - where: { id: application.id }, - data: { configHash } - }); - } - await db.prisma.application.update({ - where: { id: application.id }, - data: { updatedAt: new Date() } - }); - await buildQueue.add(buildId, { - build_id: buildId, - type: 'webhook_commit', - ...application - }); + const applicationFound = await db.getApplicationWebhook({ projectId, branch }); + if (applicationFound) { + const webhookSecret = applicationFound.gitSource.githubApp.webhookSecret; + const hmac = crypto.createHmac('sha256', webhookSecret); + const digest = Buffer.from( + 'sha256=' + hmac.update(JSON.stringify(body)).digest('hex'), + 'utf8' + ); + const checksum = Buffer.from(githubSignature, 'utf8'); + if (!dev) { + if (checksum.length !== digest.length || !crypto.timingSafeEqual(digest, checksum)) { return { - status: 200, + status: 500, body: { - message: 'Queued. Thank you!' + message: 'SHA256 checksum failed. Are you doing something fishy?' } }; - } else if (githubEvent === 'pull_request') { - const pullmergeRequestId = body.number; - const pullmergeRequestAction = body.action; - const sourceBranch = body.pull_request.head.ref; - if (!allowedActions.includes(pullmergeRequestAction)) { - return { - status: 500, - body: { - message: 'Action not allowed.' - } - }; - } - - if (application.settings.previews) { - if (application.destinationDockerId) { - const isRunning = await checkContainer( - application.destinationDocker.engine, - application.id - ); - if (!isRunning) { - return { - status: 500, - body: { - message: 'Application not running.' - } - }; - } - } - if ( - pullmergeRequestAction === 'opened' || - pullmergeRequestAction === 'reopened' || - pullmergeRequestAction === 'synchronize' - ) { - await db.prisma.application.update({ - where: { id: application.id }, - data: { updatedAt: new Date() } - }); - await buildQueue.add(buildId, { - build_id: buildId, - type: 'webhook_pr', - ...application, - sourceBranch, - pullmergeRequestId - }); - return { - status: 200, - body: { - message: 'Queued. Thank you!' - } - }; - } else if (pullmergeRequestAction === 'closed') { - if (application.destinationDockerId) { - const id = `${application.id}-${pullmergeRequestId}`; - const engine = application.destinationDocker.engine; - await removeDestinationDocker({ id, engine }); - } - return { - status: 200, - body: { - message: 'Removed preview. Thank you!' - } - }; - } - } else { - return { - status: 500, - body: { - message: 'Pull request previews are not enabled.' - } - }; - } } } - return { - status: 500, - body: { - message: 'Not handled event.' + + if (githubEvent === 'push') { + if (!applicationFound.configHash) { + const configHash = crypto + .createHash('sha256') + .update( + JSON.stringify({ + buildPack: applicationFound.buildPack, + port: applicationFound.port, + installCommand: applicationFound.installCommand, + buildCommand: applicationFound.buildCommand, + startCommand: applicationFound.startCommand + }) + ) + .digest('hex'); + await db.prisma.application.updateMany({ + where: { branch, projectId }, + data: { configHash } + }); } - }; + await db.prisma.application.update({ + where: { id: applicationFound.id }, + data: { updatedAt: new Date() } + }); + await buildQueue.add(buildId, { + build_id: buildId, + type: 'webhook_commit', + ...applicationFound + }); + return { + status: 200, + body: { + message: 'Queued. Thank you!' + } + }; + } else if (githubEvent === 'pull_request') { + const pullmergeRequestId = body.number; + const pullmergeRequestAction = body.action; + const sourceBranch = body.pull_request.head.ref; + if (!allowedActions.includes(pullmergeRequestAction)) { + return { + status: 500, + body: { + message: 'Action not allowed.' + } + }; + } + + if (applicationFound.settings.previews) { + if (applicationFound.destinationDockerId) { + const isRunning = await checkContainer( + applicationFound.destinationDocker.engine, + applicationFound.id + ); + if (!isRunning) { + return { + status: 500, + body: { + message: 'Application not running.' + } + }; + } + } + if ( + pullmergeRequestAction === 'opened' || + pullmergeRequestAction === 'reopened' || + pullmergeRequestAction === 'synchronize' + ) { + await db.prisma.application.update({ + where: { id: applicationFound.id }, + data: { updatedAt: new Date() } + }); + await buildQueue.add(buildId, { + build_id: buildId, + type: 'webhook_pr', + ...applicationFound, + sourceBranch, + pullmergeRequestId + }); + return { + status: 200, + body: { + message: 'Queued. Thank you!' + } + }; + } else if (pullmergeRequestAction === 'closed') { + if (applicationFound.destinationDockerId) { + const id = `${applicationFound.id}-${pullmergeRequestId}`; + const engine = applicationFound.destinationDocker.engine; + await removeDestinationDocker({ id, engine }); + } + return { + status: 200, + body: { + message: 'Removed preview. Thank you!' + } + }; + } + } else { + return { + status: 500, + body: { + message: 'Pull request previews are not enabled.' + } + }; + } + } } return { status: 500, body: { - message: 'No applications configured in Coolify.' + message: 'Not handled event.' } }; } catch (err) { diff --git a/src/routes/webhooks/gitlab/events.ts b/src/routes/webhooks/gitlab/events.ts index 8b35d1b41..1c125ec02 100644 --- a/src/routes/webhooks/gitlab/events.ts +++ b/src/routes/webhooks/gitlab/events.ts @@ -21,46 +21,42 @@ export const options: RequestHandler = async () => { export const post: RequestHandler = async (event) => { const allowedActions = ['opened', 'reopen', 'close', 'open', 'update']; const body = await event.request.json(); - + const buildId = cuid(); try { const { object_kind: objectKind } = body; if (objectKind === 'push') { const { ref } = body; const projectId = Number(body['project_id']); const branch = ref.split('/')[2]; - const applications = await db.getApplicationWebhook({ projectId, branch }); - if (applications.length > 0) { - for (const application of applications) { - const buildId = cuid(); - if (!application.configHash) { - const configHash = crypto - .createHash('sha256') - .update( - JSON.stringify({ - buildPack: application.buildPack, - port: application.port, - installCommand: application.installCommand, - buildCommand: application.buildCommand, - startCommand: application.startCommand - }) - ) - .digest('hex'); - await db.prisma.application.update({ - where: { id: application.id }, - data: { configHash } - }); - } - await db.prisma.application.update({ - where: { id: application.id }, - data: { updatedAt: new Date() } - }); - await buildQueue.add(buildId, { - build_id: buildId, - type: 'webhook_commit', - ...application + const applicationFound = await db.getApplicationWebhook({ projectId, branch }); + if (applicationFound) { + if (!applicationFound.configHash) { + const configHash = crypto + .createHash('sha256') + .update( + JSON.stringify({ + buildPack: applicationFound.buildPack, + port: applicationFound.port, + installCommand: applicationFound.installCommand, + buildCommand: applicationFound.buildCommand, + startCommand: applicationFound.startCommand + }) + ) + .digest('hex'); + await db.prisma.application.updateMany({ + where: { branch, projectId }, + data: { configHash } }); } - + await db.prisma.application.update({ + where: { id: applicationFound.id }, + data: { updatedAt: new Date() } + }); + await buildQueue.add(buildId, { + build_id: buildId, + type: 'webhook_commit', + ...applicationFound + }); return { status: 200, body: { @@ -68,12 +64,6 @@ export const post: RequestHandler = async (event) => { } }; } - return { - status: 500, - body: { - message: 'No applications configured in Coolify.' - } - }; } else if (objectKind === 'merge_request') { const webhookToken = event.request.headers.get('x-gitlab-token'); if (!webhookToken) { @@ -108,73 +98,69 @@ export const post: RequestHandler = async (event) => { }; } - const applications = await db.getApplicationWebhook({ projectId, branch: targetBranch }); - if (applications.length > 0) { - for (const application of applications) { - const buildId = cuid(); - if (application.settings.previews) { - if (application.destinationDockerId) { - const isRunning = await checkContainer( - application.destinationDocker.engine, - application.id - ); - if (!isRunning) { - return { - status: 500, - body: { - message: 'Application not running.' - } - }; - } - } - if (!dev && application.gitSource.gitlabApp.webhookToken !== webhookToken) { + const applicationFound = await db.getApplicationWebhook({ projectId, branch: targetBranch }); + if (applicationFound) { + if (applicationFound.settings.previews) { + if (applicationFound.destinationDockerId) { + const isRunning = await checkContainer( + applicationFound.destinationDocker.engine, + applicationFound.id + ); + if (!isRunning) { return { status: 500, body: { - message: 'Ooops, something is not okay, are you okay?' - } - }; - } - if ( - action === 'opened' || - action === 'reopen' || - action === 'open' || - action === 'update' - ) { - await db.prisma.application.update({ - where: { id: application.id }, - data: { updatedAt: new Date() } - }); - await buildQueue.add(buildId, { - build_id: buildId, - type: 'webhook_mr', - ...application, - sourceBranch, - pullmergeRequestId - }); - return { - status: 200, - body: { - message: 'Queued. Thank you!' - } - }; - } else if (action === 'close') { - if (application.destinationDockerId) { - const id = `${application.id}-${pullmergeRequestId}`; - const engine = application.destinationDocker.engine; - await removeDestinationDocker({ id, engine }); - } - - return { - status: 200, - body: { - message: 'Removed preview. Thank you!' + message: 'Application not running.' } }; } } - } + if (!dev && applicationFound.gitSource.gitlabApp.webhookToken !== webhookToken) { + return { + status: 500, + body: { + message: 'Ooops, something is not okay, are you okay?' + } + }; + } + if ( + action === 'opened' || + action === 'reopen' || + action === 'open' || + action === 'update' + ) { + await db.prisma.application.update({ + where: { id: applicationFound.id }, + data: { updatedAt: new Date() } + }); + await buildQueue.add(buildId, { + build_id: buildId, + type: 'webhook_mr', + ...applicationFound, + sourceBranch, + pullmergeRequestId + }); + return { + status: 200, + body: { + message: 'Queued. Thank you!' + } + }; + } else if (action === 'close') { + if (applicationFound.destinationDockerId) { + const id = `${applicationFound.id}-${pullmergeRequestId}`; + const engine = applicationFound.destinationDocker.engine; + await removeDestinationDocker({ id, engine }); + } + return { + status: 200, + body: { + message: 'Removed preview. Thank you!' + } + }; + } + } return { status: 500, body: { From 36c7e1a3c326af46b50bc9c2fc2b732ee85a6cf6 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 11 Mar 2022 23:55:57 +0100 Subject: [PATCH 09/10] feat: Install pnpm into docker image if pnpm lock file is used --- src/lib/buildPacks/nestjs.ts | 6 ++++++ src/lib/buildPacks/nextjs.ts | 9 ++++++++- src/lib/buildPacks/node.ts | 9 ++++++++- src/lib/buildPacks/nuxtjs.ts | 9 ++++++++- src/lib/components/templates.ts | 2 +- src/lib/docker.ts | 6 +++++- 6 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/lib/buildPacks/nestjs.ts b/src/lib/buildPacks/nestjs.ts index 38ef3bce1..b30c5ecd9 100644 --- a/src/lib/buildPacks/nestjs.ts +++ b/src/lib/buildPacks/nestjs.ts @@ -4,13 +4,19 @@ import { promises as fs } from 'fs'; const createDockerfile = async (data, image): Promise => { const { applicationId, tag, port, startCommand, workdir, baseDirectory } = data; const Dockerfile: Array = []; + const isPnpm = startCommand.includes('pnpm'); Dockerfile.push(`FROM ${image}`); Dockerfile.push('WORKDIR /usr/src/app'); Dockerfile.push(`LABEL coolify.image=true`); + if (isPnpm) { + Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm'); + Dockerfile.push('RUN pnpm add -g pnpm'); + } Dockerfile.push( `COPY --from=${applicationId}:${tag}-cache /usr/src/app/${baseDirectory || ''} ./` ); + Dockerfile.push(`EXPOSE ${port}`); Dockerfile.push(`CMD ${startCommand}`); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); diff --git a/src/lib/buildPacks/nextjs.ts b/src/lib/buildPacks/nextjs.ts index 15e6dacca..c8d16dab6 100644 --- a/src/lib/buildPacks/nextjs.ts +++ b/src/lib/buildPacks/nextjs.ts @@ -13,7 +13,10 @@ const createDockerfile = async (data, image): Promise => { pullmergeRequestId } = data; const Dockerfile: Array = []; - + const isPnpm = + installCommand.includes('pnpm') || + buildCommand.includes('pnpm') || + startCommand.includes('pnpm'); Dockerfile.push(`FROM ${image}`); Dockerfile.push('WORKDIR /usr/src/app'); Dockerfile.push(`LABEL coolify.image=true`); @@ -32,6 +35,10 @@ const createDockerfile = async (data, image): Promise => { } }); } + if (isPnpm) { + Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm'); + Dockerfile.push('RUN pnpm add -g pnpm'); + } Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`); try { await fs.stat(`${workdir}/yarn.lock`); diff --git a/src/lib/buildPacks/node.ts b/src/lib/buildPacks/node.ts index 15e6dacca..c8d16dab6 100644 --- a/src/lib/buildPacks/node.ts +++ b/src/lib/buildPacks/node.ts @@ -13,7 +13,10 @@ const createDockerfile = async (data, image): Promise => { pullmergeRequestId } = data; const Dockerfile: Array = []; - + const isPnpm = + installCommand.includes('pnpm') || + buildCommand.includes('pnpm') || + startCommand.includes('pnpm'); Dockerfile.push(`FROM ${image}`); Dockerfile.push('WORKDIR /usr/src/app'); Dockerfile.push(`LABEL coolify.image=true`); @@ -32,6 +35,10 @@ const createDockerfile = async (data, image): Promise => { } }); } + if (isPnpm) { + Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm'); + Dockerfile.push('RUN pnpm add -g pnpm'); + } Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`); try { await fs.stat(`${workdir}/yarn.lock`); diff --git a/src/lib/buildPacks/nuxtjs.ts b/src/lib/buildPacks/nuxtjs.ts index 15e6dacca..c8d16dab6 100644 --- a/src/lib/buildPacks/nuxtjs.ts +++ b/src/lib/buildPacks/nuxtjs.ts @@ -13,7 +13,10 @@ const createDockerfile = async (data, image): Promise => { pullmergeRequestId } = data; const Dockerfile: Array = []; - + const isPnpm = + installCommand.includes('pnpm') || + buildCommand.includes('pnpm') || + startCommand.includes('pnpm'); Dockerfile.push(`FROM ${image}`); Dockerfile.push('WORKDIR /usr/src/app'); Dockerfile.push(`LABEL coolify.image=true`); @@ -32,6 +35,10 @@ const createDockerfile = async (data, image): Promise => { } }); } + if (isPnpm) { + Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm'); + Dockerfile.push('RUN pnpm add -g pnpm'); + } Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`); try { await fs.stat(`${workdir}/yarn.lock`); diff --git a/src/lib/components/templates.ts b/src/lib/components/templates.ts index f09272634..1b2e7346d 100644 --- a/src/lib/components/templates.ts +++ b/src/lib/components/templates.ts @@ -13,7 +13,7 @@ export function findBuildPack(pack, packageManager = 'npm') { if (pack === 'node') { return { ...metaData, - installCommand: null, + ...defaultBuildAndDeploy(packageManager), buildCommand: null, startCommand: null, publishDirectory: null, diff --git a/src/lib/docker.ts b/src/lib/docker.ts index 477491f12..a202649e2 100644 --- a/src/lib/docker.ts +++ b/src/lib/docker.ts @@ -16,6 +16,7 @@ export async function buildCacheImageWithNode(data, imageForBuild) { secrets, pullmergeRequestId } = data; + const isPnpm = installCommand.includes('pnpm') || buildCommand.includes('pnpm'); const Dockerfile: Array = []; Dockerfile.push(`FROM ${imageForBuild}`); Dockerfile.push('WORKDIR /usr/src/app'); @@ -35,7 +36,10 @@ export async function buildCacheImageWithNode(data, imageForBuild) { } }); } - // TODO: If build command defined, install command should be the default yarn install + if (isPnpm) { + Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm'); + Dockerfile.push('RUN pnpm add -g pnpm'); + } if (installCommand) { Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`); try { From 1bc9e4c2d3a78671c4afa0aa8457dfdb6370cb58 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 11 Mar 2022 23:56:11 +0100 Subject: [PATCH 10/10] fix: Autodeploy true by default for GH repos --- .../[id]/configuration/_GithubRepositories.svelte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routes/applications/[id]/configuration/_GithubRepositories.svelte b/src/routes/applications/[id]/configuration/_GithubRepositories.svelte index 9ba619d64..f69db0476 100644 --- a/src/routes/applications/[id]/configuration/_GithubRepositories.svelte +++ b/src/routes/applications/[id]/configuration/_GithubRepositories.svelte @@ -25,10 +25,10 @@ let selected = { projectId: undefined, repository: undefined, - branch: undefined + branch: undefined, + autodeploy: application.settings.autodeploy || true }; let showSave = false; - let autodeploy = application.settings.autodeploy || true; async function loadRepositoriesByPage(page = 0) { return await get(`${apiUrl}/installation/repositories?per_page=100&page=${page}`, { @@ -75,7 +75,7 @@ `This branch is already used by another application. Webhooks won't work in this case for both applications. Are you sure you want to use it?` ); if (sure) { - autodeploy = false; + selected.autodeploy = false; showSave = true; return true; }