diff --git a/apps/api/prisma/migrations/20230307101148_add_host_volumes/migration.sql b/apps/api/prisma/migrations/20230307101148_add_host_volumes/migration.sql new file mode 100644 index 000000000..220ad995c --- /dev/null +++ b/apps/api/prisma/migrations/20230307101148_add_host_volumes/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "ApplicationPersistentStorage" ADD COLUMN "hostPath" TEXT; diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index bb76d83b0..7dca8314b 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -195,6 +195,7 @@ model ApplicationSettings { model ApplicationPersistentStorage { id String @id @default(cuid()) applicationId String + hostPath String? path String oldPath Boolean @default(false) createdAt DateTime @default(now()) diff --git a/apps/api/src/jobs/deployApplication.ts b/apps/api/src/jobs/deployApplication.ts index 8fb71cfe6..d3de46268 100644 --- a/apps/api/src/jobs/deployApplication.ts +++ b/apps/api/src/jobs/deployApplication.ts @@ -110,6 +110,9 @@ import * as buildpacks from '../lib/buildPacks'; .replace(/\//gi, '-') .replace('-app', '')}:${storage.path}`; } + if (storage.hostPath) { + return `${storage.hostPath}:${storage.path}` + } return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`; }) || []; @@ -160,7 +163,11 @@ import * as buildpacks from '../lib/buildPacks'; port: exposePort ? `${exposePort}:${port}` : port }); try { - const composeVolumes = volumes.map((volume) => { + const composeVolumes = volumes.filter(v => { + if (!v.startsWith('.') && !v.startsWith('..') && !v.startsWith('/') && !v.startsWith('~')) { + return v; + } + }).map((volume) => { return { [`${volume.split(':')[0]}`]: { name: volume.split(':')[0] @@ -381,6 +388,9 @@ import * as buildpacks from '../lib/buildPacks'; .replace(/\//gi, '-') .replace('-app', '')}:${storage.path}`; } + if (storage.hostPath) { + return `${storage.hostPath}:${storage.path}` + } return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`; }) || []; @@ -691,7 +701,11 @@ import * as buildpacks from '../lib/buildPacks'; await saveDockerRegistryCredentials({ url, username, password, workdir }); } try { - const composeVolumes = volumes.map((volume) => { + const composeVolumes = volumes.filter(v => { + if (!v.startsWith('.') && !v.startsWith('..') && !v.startsWith('/') && !v.startsWith('~')) { + return v; + } + }).map((volume) => { return { [`${volume.split(':')[0]}`]: { name: volume.split(':')[0] diff --git a/apps/api/src/lib/buildPacks/compose.ts b/apps/api/src/lib/buildPacks/compose.ts index 70a2ba8af..49d4a715b 100644 --- a/apps/api/src/lib/buildPacks/compose.ts +++ b/apps/api/src/lib/buildPacks/compose.ts @@ -36,12 +36,13 @@ export default async function (data) { if (volumes.length > 0) { for (const volume of volumes) { let [v, path] = volume.split(':'); - composeVolumes[v] = { - name: v - }; + if (!v.startsWith('.') && !v.startsWith('..') && !v.startsWith('/') && !v.startsWith('~')) { + composeVolumes[v] = { + name: v + }; + } } } - let networks = {}; for (let [key, value] of Object.entries(dockerComposeYaml.services)) { value['container_name'] = `${applicationId}-${key}`; @@ -78,6 +79,7 @@ export default async function (data) { if (value['volumes']?.length > 0) { value['volumes'] = value['volumes'].map((volume) => { let [v, path, permission] = volume.split(':'); + console.log(v, path, permission) if ( v.startsWith('.') || v.startsWith('..') || @@ -106,6 +108,7 @@ export default async function (data) { value['volumes'].push(volume); } } + console.log({ volumes, composeVolumes }) if (dockerComposeConfiguration[key]?.port) { value['expose'] = [dockerComposeConfiguration[key].port]; } diff --git a/apps/api/src/lib/common.ts b/apps/api/src/lib/common.ts index 8b931653d..5626d16ec 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -1633,6 +1633,9 @@ export function errorHandler({ type?: string | null; }) { if (message.message) message = message.message; + if (message.includes('Unique constraint failed')) { + message = 'This data is unique and already exists. Please try again with a different value.'; + } if (type === 'normal') { Sentry.captureException(message); } diff --git a/apps/api/src/routes/api/v1/applications/handlers.ts b/apps/api/src/routes/api/v1/applications/handlers.ts index 886b7bc88..8621a93ca 100644 --- a/apps/api/src/routes/api/v1/applications/handlers.ts +++ b/apps/api/src/routes/api/v1/applications/handlers.ts @@ -1340,16 +1340,16 @@ export async function getStorages(request: FastifyRequest) { export async function saveStorage(request: FastifyRequest, reply: FastifyReply) { try { const { id } = request.params; - const { path, newStorage, storageId } = request.body; + const { hostPath, path, newStorage, storageId } = request.body; if (newStorage) { await prisma.applicationPersistentStorage.create({ - data: { path, application: { connect: { id } } } + data: { hostPath, path, application: { connect: { id } } } }); } else { await prisma.applicationPersistentStorage.update({ where: { id: storageId }, - data: { path } + data: { hostPath, path } }); } return reply.code(201).send(); diff --git a/apps/api/src/routes/api/v1/applications/types.ts b/apps/api/src/routes/api/v1/applications/types.ts index 517194bd6..1c42f468a 100644 --- a/apps/api/src/routes/api/v1/applications/types.ts +++ b/apps/api/src/routes/api/v1/applications/types.ts @@ -96,6 +96,7 @@ export interface DeleteSecret extends OnlyId { } export interface SaveStorage extends OnlyId { Body: { + hostPath?: string; path: string; newStorage: boolean; storageId: string; diff --git a/apps/ui/src/routes/applications/[id]/_Storage.svelte b/apps/ui/src/routes/applications/[id]/_Storage.svelte index 0e0e13b8b..6eeb2c66b 100644 --- a/apps/ui/src/routes/applications/[id]/_Storage.svelte +++ b/apps/ui/src/routes/applications/[id]/_Storage.svelte @@ -12,6 +12,7 @@ import { errorNotification } from '$lib/common'; import { addToast } from '$lib/store'; import CopyVolumeField from '$lib/components/CopyVolumeField.svelte'; + import SimpleExplainer from '$lib/components/SimpleExplainer.svelte'; const { id } = $page.params; let isHttps = browser && window.location.protocol === 'https:'; export let value: string; @@ -33,11 +34,13 @@ storage.path.replace(/\/\//g, '/'); await post(`/applications/${id}/storages`, { path: storage.path, + hostPath: storage.hostPath, storageId: storage.id, newStorage }); dispatch('refresh'); if (isNew) { + storage.hostPath = null; storage.path = null; storage.id = null; } @@ -80,27 +83,42 @@
{#if storage.applicationId} {#if storage.oldPath} - - + {:else if !storage.hostPath} + - {:else} - - {/if} {/if} + + {#if isNew} +
+ + + +
+ {:else if storage.hostPath} + + {/if} -
+
{#if isNew}