diff --git a/package.json b/package.json index b936ec3bc..b3945abee 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.15", + "version": "2.0.16", "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", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index cf9684f53..7599602fb 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -109,13 +109,14 @@ model Secret { id String @id @default(cuid()) name String value String + isPRMRSecret Boolean @default(false) isBuildSecret Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt application Application @relation(fields: [applicationId], references: [id]) applicationId String - @@unique([name, applicationId]) + @@unique([name, applicationId, isPRMRSecret]) } model BuildLog { diff --git a/src/lib/database/checks.ts b/src/lib/database/checks.ts index 35af8dc00..d825de701 100644 --- a/src/lib/database/checks.ts +++ b/src/lib/database/checks.ts @@ -15,8 +15,8 @@ export async function isDockerNetworkExists({ network }) { return await prisma.destinationDocker.findFirst({ where: { network } }); } -export async function isSecretExists({ id, name }) { - return await prisma.secret.findFirst({ where: { name, applicationId: id } }); +export async function isSecretExists({ id, name, isPRMRSecret }) { + return await prisma.secret.findFirst({ where: { name, applicationId: id, isPRMRSecret } }); } export async function isDomainConfigured({ id, fqdn }) { diff --git a/src/lib/database/secrets.ts b/src/lib/database/secrets.ts index c03ec9459..ae5abe05b 100644 --- a/src/lib/database/secrets.ts +++ b/src/lib/database/secrets.ts @@ -1,21 +1,35 @@ import { encrypt } from '$lib/crypto'; import { prisma } from './common'; -export async function listSecrets({ applicationId }) { +export async function listSecrets(applicationId: string) { return await prisma.secret.findMany({ where: { applicationId }, - orderBy: { createdAt: 'desc' }, - select: { id: true, createdAt: true, name: true, isBuildSecret: true } + orderBy: { createdAt: 'desc' } }); } -export async function createSecret({ id, name, value, isBuildSecret }) { +export async function createSecret({ id, name, value, isBuildSecret, isPRMRSecret }) { value = encrypt(value); return await prisma.secret.create({ - data: { name, value, isBuildSecret, application: { connect: { id } } } + data: { name, value, isBuildSecret, isPRMRSecret, application: { connect: { id } } } }); } +export async function updateSecret({ id, name, value, isBuildSecret, isPRMRSecret }) { + value = encrypt(value); + const found = await prisma.secret.findFirst({ where: { applicationId: id, name, isPRMRSecret } }); + if (found) { + return await prisma.secret.updateMany({ + where: { applicationId: id, name, isPRMRSecret }, + data: { value, isBuildSecret, isPRMRSecret } + }); + } else { + return await prisma.secret.create({ + data: { name, value, isBuildSecret, isPRMRSecret, application: { connect: { id } } } + }); + } +} + export async function removeSecret({ id, name }) { return await prisma.secret.deleteMany({ where: { applicationId: id, name } }); } diff --git a/src/routes/applications/[id]/previews/index.json.ts b/src/routes/applications/[id]/previews/index.json.ts index fd5bd45b0..c50b01d7b 100644 --- a/src/routes/applications/[id]/previews/index.json.ts +++ b/src/routes/applications/[id]/previews/index.json.ts @@ -11,6 +11,9 @@ export const get: RequestHandler = async (event) => { const { id } = event.params; try { + const secrets = await db.listSecrets(id); + const applicationSecrets = secrets.filter((secret) => !secret.isPRMRSecret); + const PRMRSecrets = secrets.filter((secret) => secret.isPRMRSecret); const destinationDocker = await db.getDestinationByApplicationId({ id, teamId }); const docker = dockerInstance({ destinationDocker }); const listContainers = await docker.engine.listContainers({ @@ -35,7 +38,9 @@ export const get: RequestHandler = async (event) => { }); return { body: { - containers: jsonContainers + containers: jsonContainers, + applicationSecrets, + PRMRSecrets } }; } catch (error) { diff --git a/src/routes/applications/[id]/previews/index.svelte b/src/routes/applications/[id]/previews/index.svelte index 28c5efd6f..581416050 100644 --- a/src/routes/applications/[id]/previews/index.svelte +++ b/src/routes/applications/[id]/previews/index.svelte @@ -22,8 +22,18 @@
@@ -31,8 +41,56 @@ Previews for {getDomain(application.fqdn)}
- -
+
+ Preview secrets. They will overwrite application secrets for PR/MR deployments. Useful for + creating staging environments for these deployments. +
+
+ + + + + + + + + + {#each applicationSecrets as secret} + {#key secret.id} + + s.name === secret.name)} + isPRMRSecret + name={secret.name} + value={secret.value ? secret.value : 'ENCRYPTED'} + isBuildSecret={secret.isBuildSecret} + on:refresh={refreshSecrets} + /> + + {/key} + {/each} + + +
NameValueNeed during buildtime? +
+
+
{#if containers.length > 0} {#each containers as container} diff --git a/src/routes/applications/[id]/secrets/_Secret.svelte b/src/routes/applications/[id]/secrets/_Secret.svelte index 60c53080b..b5cbd8751 100644 --- a/src/routes/applications/[id]/secrets/_Secret.svelte +++ b/src/routes/applications/[id]/secrets/_Secret.svelte @@ -3,6 +3,11 @@ export let value = ''; export let isBuildSecret = false; export let isNewSecret = false; + export let isPRMRSecret = false; + export let PRMRSecret = {}; + + if (isPRMRSecret) value = PRMRSecret.value; + import { page } from '$app/stores'; import { del, post } from '$lib/api'; import { errorNotification } from '$lib/form'; @@ -36,7 +41,7 @@ } try { - await post(`/applications/${id}/secrets.json`, { name, value, isBuildSecret }); + await post(`/applications/${id}/secrets.json`, { name, value, isBuildSecret, isPRMRSecret }); dispatch('refresh'); if (isNewSecret) { name = ''; @@ -75,9 +80,9 @@ required placeholder="J$#@UIO%HO#$U%H" class="-mx-2 w-64 border-2 border-transparent" - class:bg-transparent={!isNewSecret} - class:cursor-not-allowed={!isNewSecret} - readonly={!isNewSecret} + class:bg-transparent={!isNewSecret && !isPRMRSecret} + class:cursor-not-allowed={!isNewSecret && !isPRMRSecret} + readonly={!isNewSecret && !isPRMRSecret} /> @@ -134,6 +139,10 @@
+ {:else if isPRMRSecret} +
+ +
{:else}
diff --git a/src/routes/applications/[id]/secrets/index.json.ts b/src/routes/applications/[id]/secrets/index.json.ts index bed54b950..365a74c87 100644 --- a/src/routes/applications/[id]/secrets/index.json.ts +++ b/src/routes/applications/[id]/secrets/index.json.ts @@ -7,8 +7,9 @@ export const get: RequestHandler = async (event) => { const { teamId, status, body } = await getUserDetails(event); if (status === 401) return { status, body }; + const { id } = event.params; try { - const secrets = await db.listSecrets({ applicationId: event.params.id }); + const secrets = await (await db.listSecrets(id)).filter((secret) => !secret.isPRMRSecret); return { status: 200, body: { @@ -27,16 +28,23 @@ export const post: RequestHandler = async (event) => { if (status === 401) return { status, body }; const { id } = event.params; - const { name, value, isBuildSecret } = await event.request.json(); + const { name, value, isBuildSecret, isPRMRSecret } = await event.request.json(); try { - const found = await db.isSecretExists({ id, name }); - if (found) { - throw { - error: `Secret ${name} already exists.` - }; + if (!isPRMRSecret) { + const found = await db.isSecretExists({ id, name, isPRMRSecret }); + if (found) { + throw { + error: `Secret ${name} already exists.` + }; + } else { + await db.createSecret({ id, name, value, isBuildSecret, isPRMRSecret }); + return { + status: 201 + }; + } } else { - await db.createSecret({ id, name, value, isBuildSecret }); + await db.updateSecret({ id, name, value, isBuildSecret, isPRMRSecret }); return { status: 201 };