From 15e69c538a3292edf40c5e58bce58cedaf0fbdd0 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Sat, 19 Feb 2022 14:54:47 +0100 Subject: [PATCH 1/5] feat: Preview secrets chore: version++ --- package.json | 2 +- prisma/schema.prisma | 3 +- src/lib/database/checks.ts | 4 +- src/lib/database/secrets.ts | 24 +++++-- .../applications/[id]/previews/index.json.ts | 7 +- .../applications/[id]/previews/index.svelte | 64 ++++++++++++++++++- .../applications/[id]/secrets/_Secret.svelte | 17 +++-- .../applications/[id]/secrets/index.json.ts | 24 ++++--- 8 files changed, 120 insertions(+), 25 deletions(-) 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 }; From cab7ac7d58140b6fd4c96cedabaa11b437d2ab19 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Sat, 19 Feb 2022 22:37:45 +0100 Subject: [PATCH 2/5] fix: If DNS not found, do not redirect --- src/routes/settings/index.json.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/routes/settings/index.json.ts b/src/routes/settings/index.json.ts index 217b314bd..bd365d5e4 100644 --- a/src/routes/settings/index.json.ts +++ b/src/routes/settings/index.json.ts @@ -1,3 +1,4 @@ +import { dev } from '$app/env'; import { getDomain, getUserDetails } from '$lib/common'; import * as db from '$lib/database'; import { listSettings, ErrorHandler } from '$lib/database'; @@ -43,7 +44,13 @@ export const del: RequestHandler = async (event) => { if (status === 401) return { status, body }; const { fqdn } = await event.request.json(); - const ip = await dns.resolve(event.url.hostname); + let ip; + console.log(fqdn); + try { + ip = await dns.resolve(fqdn); + } catch (error) { + // Do not care. + } try { const domain = getDomain(fqdn); await db.prisma.setting.update({ where: { fqdn }, data: { fqdn: null } }); @@ -53,7 +60,7 @@ export const del: RequestHandler = async (event) => { status: 200, body: { message: 'Domain removed', - redirect: `http://${ip[0]}:3000/settings` + redirect: ip ? `http://${ip[0]}:3000/settings` : undefined } }; } catch (error) { From 7c683668ebfde005555007004160fc3c04d45bd1 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Sun, 20 Feb 2022 00:00:31 +0100 Subject: [PATCH 3/5] feat: Secrets for previews UI: Some CSS changes --- src/lib/buildPacks/docker.ts | 13 +- src/lib/buildPacks/nextjs.ts | 22 +++- src/lib/buildPacks/node.ts | 22 +++- src/lib/buildPacks/nuxtjs.ts | 22 +++- src/lib/buildPacks/static.ts | 22 +++- src/lib/components/CopyPasswordField.svelte | 124 +++++++++--------- src/lib/components/templates.ts | 1 - src/lib/database/secrets.ts | 12 +- src/lib/docker.ts | 15 ++- src/lib/queues/builder.ts | 11 +- src/routes/applications/[id]/index.svelte | 6 +- .../applications/[id]/previews/index.json.ts | 8 +- .../applications/[id]/previews/index.svelte | 21 +-- .../applications/[id]/secrets/_Secret.svelte | 59 +++++---- .../applications/[id]/secrets/index.json.ts | 5 +- .../applications/[id]/secrets/index.svelte | 4 +- .../databases/[id]/_Databases/_CouchDb.svelte | 2 +- .../databases/[id]/_Databases/_MongoDB.svelte | 2 +- .../databases/[id]/_Databases/_MySQL.svelte | 2 +- .../[id]/_Databases/_PostgreSQL.svelte | 2 +- .../databases/[id]/_Databases/_Redis.svelte | 2 +- .../services/[id]/_Services/_MinIO.svelte | 6 +- src/routes/settings/index.svelte | 2 +- src/tailwind.css | 4 +- 24 files changed, 243 insertions(+), 146 deletions(-) diff --git a/src/lib/buildPacks/docker.ts b/src/lib/buildPacks/docker.ts index 5bc6ce453..af000e551 100644 --- a/src/lib/buildPacks/docker.ts +++ b/src/lib/buildPacks/docker.ts @@ -9,7 +9,8 @@ export default async function ({ docker, buildId, baseDirectory, - secrets + secrets, + pullmergeRequestId }) { try { let file = `${workdir}/Dockerfile`; @@ -24,7 +25,15 @@ export default async function ({ if (secrets.length > 0) { secrets.forEach((secret) => { if (secret.isBuildSecret) { - Dockerfile.push(`ARG ${secret.name} ${secret.value}`); + if (pullmergeRequestId) { + if (secret.isPRMRSecret) { + Dockerfile.push(`ARG ${secret.name} ${secret.value}`); + } + } else { + if (!secret.isPRMRSecret) { + Dockerfile.push(`ARG ${secret.name} ${secret.value}`); + } + } } }); } diff --git a/src/lib/buildPacks/nextjs.ts b/src/lib/buildPacks/nextjs.ts index 53a7f884e..648208d83 100644 --- a/src/lib/buildPacks/nextjs.ts +++ b/src/lib/buildPacks/nextjs.ts @@ -2,8 +2,16 @@ import { buildImage } from '$lib/docker'; import { promises as fs } from 'fs'; const createDockerfile = async (data, image): Promise => { - const { workdir, port, installCommand, buildCommand, startCommand, baseDirectory, secrets } = - data; + const { + workdir, + port, + installCommand, + buildCommand, + startCommand, + baseDirectory, + secrets, + pullmergeRequestId + } = data; const Dockerfile: Array = []; Dockerfile.push(`FROM ${image}`); @@ -11,7 +19,15 @@ const createDockerfile = async (data, image): Promise => { if (secrets.length > 0) { secrets.forEach((secret) => { if (secret.isBuildSecret) { - Dockerfile.push(`ARG ${secret.name} ${secret.value}`); + if (pullmergeRequestId) { + if (secret.isPRMRSecret) { + Dockerfile.push(`ARG ${secret.name} ${secret.value}`); + } + } else { + if (!secret.isPRMRSecret) { + Dockerfile.push(`ARG ${secret.name} ${secret.value}`); + } + } } }); } diff --git a/src/lib/buildPacks/node.ts b/src/lib/buildPacks/node.ts index 53a7f884e..648208d83 100644 --- a/src/lib/buildPacks/node.ts +++ b/src/lib/buildPacks/node.ts @@ -2,8 +2,16 @@ import { buildImage } from '$lib/docker'; import { promises as fs } from 'fs'; const createDockerfile = async (data, image): Promise => { - const { workdir, port, installCommand, buildCommand, startCommand, baseDirectory, secrets } = - data; + const { + workdir, + port, + installCommand, + buildCommand, + startCommand, + baseDirectory, + secrets, + pullmergeRequestId + } = data; const Dockerfile: Array = []; Dockerfile.push(`FROM ${image}`); @@ -11,7 +19,15 @@ const createDockerfile = async (data, image): Promise => { if (secrets.length > 0) { secrets.forEach((secret) => { if (secret.isBuildSecret) { - Dockerfile.push(`ARG ${secret.name} ${secret.value}`); + if (pullmergeRequestId) { + if (secret.isPRMRSecret) { + Dockerfile.push(`ARG ${secret.name} ${secret.value}`); + } + } else { + if (!secret.isPRMRSecret) { + Dockerfile.push(`ARG ${secret.name} ${secret.value}`); + } + } } }); } diff --git a/src/lib/buildPacks/nuxtjs.ts b/src/lib/buildPacks/nuxtjs.ts index 53a7f884e..648208d83 100644 --- a/src/lib/buildPacks/nuxtjs.ts +++ b/src/lib/buildPacks/nuxtjs.ts @@ -2,8 +2,16 @@ import { buildImage } from '$lib/docker'; import { promises as fs } from 'fs'; const createDockerfile = async (data, image): Promise => { - const { workdir, port, installCommand, buildCommand, startCommand, baseDirectory, secrets } = - data; + const { + workdir, + port, + installCommand, + buildCommand, + startCommand, + baseDirectory, + secrets, + pullmergeRequestId + } = data; const Dockerfile: Array = []; Dockerfile.push(`FROM ${image}`); @@ -11,7 +19,15 @@ const createDockerfile = async (data, image): Promise => { if (secrets.length > 0) { secrets.forEach((secret) => { if (secret.isBuildSecret) { - Dockerfile.push(`ARG ${secret.name} ${secret.value}`); + if (pullmergeRequestId) { + if (secret.isPRMRSecret) { + Dockerfile.push(`ARG ${secret.name} ${secret.value}`); + } + } else { + if (!secret.isPRMRSecret) { + Dockerfile.push(`ARG ${secret.name} ${secret.value}`); + } + } } }); } diff --git a/src/lib/buildPacks/static.ts b/src/lib/buildPacks/static.ts index 0db1a0c83..d71eea6e1 100644 --- a/src/lib/buildPacks/static.ts +++ b/src/lib/buildPacks/static.ts @@ -2,8 +2,16 @@ import { buildCacheImageWithNode, buildImage } from '$lib/docker'; import { promises as fs } from 'fs'; const createDockerfile = async (data, image): Promise => { - const { applicationId, tag, workdir, buildCommand, baseDirectory, publishDirectory, secrets } = - data; + const { + applicationId, + tag, + workdir, + buildCommand, + baseDirectory, + publishDirectory, + secrets, + pullmergeRequestId + } = data; const Dockerfile: Array = []; Dockerfile.push(`FROM ${image}`); @@ -11,7 +19,15 @@ const createDockerfile = async (data, image): Promise => { if (secrets.length > 0) { secrets.forEach((secret) => { if (secret.isBuildSecret) { - Dockerfile.push(`ARG ${secret.name} ${secret.value}`); + if (pullmergeRequestId) { + if (secret.isPRMRSecret) { + Dockerfile.push(`ARG ${secret.name} ${secret.value}`); + } + } else { + if (!secret.isPRMRSecret) { + Dockerfile.push(`ARG ${secret.name} ${secret.value}`); + } + } } }); } diff --git a/src/lib/components/CopyPasswordField.svelte b/src/lib/components/CopyPasswordField.svelte index ac2ba94d4..648fc3278 100644 --- a/src/lib/components/CopyPasswordField.svelte +++ b/src/lib/components/CopyPasswordField.svelte @@ -1,9 +1,9 @@ -
showActions(true)} - on:mouseleave={() => showActions(false)} -> +
{#if !isPasswordField || showPassword} {#if textarea}