feat: add host path to any container
This commit is contained in:
		
							parent
							
								
									3e81d7e9cb
								
							
						
					
					
						commit
						1c237affb4
					
				| @ -0,0 +1,2 @@ | |||||||
|  | -- AlterTable | ||||||
|  | ALTER TABLE "ApplicationPersistentStorage" ADD COLUMN "hostPath" TEXT; | ||||||
| @ -195,6 +195,7 @@ model ApplicationSettings { | |||||||
| model ApplicationPersistentStorage { | model ApplicationPersistentStorage { | ||||||
|   id            String      @id @default(cuid()) |   id            String      @id @default(cuid()) | ||||||
|   applicationId String |   applicationId String | ||||||
|  |   hostPath      String? | ||||||
|   path          String |   path          String | ||||||
|   oldPath       Boolean     @default(false) |   oldPath       Boolean     @default(false) | ||||||
|   createdAt     DateTime    @default(now()) |   createdAt     DateTime    @default(now()) | ||||||
|  | |||||||
| @ -110,6 +110,9 @@ import * as buildpacks from '../lib/buildPacks'; | |||||||
| 													.replace(/\//gi, '-') | 													.replace(/\//gi, '-') | ||||||
| 													.replace('-app', '')}:${storage.path}`;
 | 													.replace('-app', '')}:${storage.path}`;
 | ||||||
| 											} | 											} | ||||||
|  | 											if (storage.hostPath) { | ||||||
|  | 												return `${storage.hostPath}:${storage.path}` | ||||||
|  | 											} | ||||||
| 											return `${applicationId}${storage.path.replace(/\//gi, '-')}:${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 | 											port: exposePort ? `${exposePort}:${port}` : port | ||||||
| 										}); | 										}); | ||||||
| 										try { | 										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 { | 												return { | ||||||
| 													[`${volume.split(':')[0]}`]: { | 													[`${volume.split(':')[0]}`]: { | ||||||
| 														name: volume.split(':')[0] | 														name: volume.split(':')[0] | ||||||
| @ -381,6 +388,9 @@ import * as buildpacks from '../lib/buildPacks'; | |||||||
| 												.replace(/\//gi, '-') | 												.replace(/\//gi, '-') | ||||||
| 												.replace('-app', '')}:${storage.path}`;
 | 												.replace('-app', '')}:${storage.path}`;
 | ||||||
| 										} | 										} | ||||||
|  | 										if (storage.hostPath) { | ||||||
|  | 											return `${storage.hostPath}:${storage.path}` | ||||||
|  | 										} | ||||||
| 										return `${applicationId}${storage.path.replace(/\//gi, '-')}:${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 }); | 											await saveDockerRegistryCredentials({ url, username, password, workdir }); | ||||||
| 										} | 										} | ||||||
| 										try { | 										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 { | 												return { | ||||||
| 													[`${volume.split(':')[0]}`]: { | 													[`${volume.split(':')[0]}`]: { | ||||||
| 														name: volume.split(':')[0] | 														name: volume.split(':')[0] | ||||||
|  | |||||||
| @ -36,12 +36,13 @@ export default async function (data) { | |||||||
| 	if (volumes.length > 0) { | 	if (volumes.length > 0) { | ||||||
| 		for (const volume of volumes) { | 		for (const volume of volumes) { | ||||||
| 			let [v, path] = volume.split(':'); | 			let [v, path] = volume.split(':'); | ||||||
|  | 			if (!v.startsWith('.') && !v.startsWith('..') && !v.startsWith('/') && !v.startsWith('~')) { | ||||||
| 				composeVolumes[v] = { | 				composeVolumes[v] = { | ||||||
| 					name: v | 					name: v | ||||||
| 				}; | 				}; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 	} | ||||||
| 	let networks = {}; | 	let networks = {}; | ||||||
| 	for (let [key, value] of Object.entries(dockerComposeYaml.services)) { | 	for (let [key, value] of Object.entries(dockerComposeYaml.services)) { | ||||||
| 		value['container_name'] = `${applicationId}-${key}`; | 		value['container_name'] = `${applicationId}-${key}`; | ||||||
| @ -78,6 +79,7 @@ export default async function (data) { | |||||||
| 		if (value['volumes']?.length > 0) { | 		if (value['volumes']?.length > 0) { | ||||||
| 			value['volumes'] = value['volumes'].map((volume) => { | 			value['volumes'] = value['volumes'].map((volume) => { | ||||||
| 				let [v, path, permission] = volume.split(':'); | 				let [v, path, permission] = volume.split(':'); | ||||||
|  | 				console.log(v, path, permission) | ||||||
| 				if ( | 				if ( | ||||||
| 					v.startsWith('.') || | 					v.startsWith('.') || | ||||||
| 					v.startsWith('..') || | 					v.startsWith('..') || | ||||||
| @ -106,6 +108,7 @@ export default async function (data) { | |||||||
| 				value['volumes'].push(volume); | 				value['volumes'].push(volume); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 		console.log({ volumes, composeVolumes }) | ||||||
| 		if (dockerComposeConfiguration[key]?.port) { | 		if (dockerComposeConfiguration[key]?.port) { | ||||||
| 			value['expose'] = [dockerComposeConfiguration[key].port]; | 			value['expose'] = [dockerComposeConfiguration[key].port]; | ||||||
| 		} | 		} | ||||||
|  | |||||||
| @ -1633,6 +1633,9 @@ export function errorHandler({ | |||||||
| 	type?: string | null; | 	type?: string | null; | ||||||
| }) { | }) { | ||||||
| 	if (message.message) message = message.message; | 	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') { | 	if (type === 'normal') { | ||||||
| 		Sentry.captureException(message); | 		Sentry.captureException(message); | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -1340,16 +1340,16 @@ export async function getStorages(request: FastifyRequest<OnlyId>) { | |||||||
| export async function saveStorage(request: FastifyRequest<SaveStorage>, reply: FastifyReply) { | export async function saveStorage(request: FastifyRequest<SaveStorage>, reply: FastifyReply) { | ||||||
| 	try { | 	try { | ||||||
| 		const { id } = request.params; | 		const { id } = request.params; | ||||||
| 		const { path, newStorage, storageId } = request.body; | 		const { hostPath, path, newStorage, storageId } = request.body; | ||||||
| 
 | 
 | ||||||
| 		if (newStorage) { | 		if (newStorage) { | ||||||
| 			await prisma.applicationPersistentStorage.create({ | 			await prisma.applicationPersistentStorage.create({ | ||||||
| 				data: { path, application: { connect: { id } } } | 				data: { hostPath, path, application: { connect: { id } } } | ||||||
| 			}); | 			}); | ||||||
| 		} else { | 		} else { | ||||||
| 			await prisma.applicationPersistentStorage.update({ | 			await prisma.applicationPersistentStorage.update({ | ||||||
| 				where: { id: storageId }, | 				where: { id: storageId }, | ||||||
| 				data: { path } | 				data: { hostPath, path } | ||||||
| 			}); | 			}); | ||||||
| 		} | 		} | ||||||
| 		return reply.code(201).send(); | 		return reply.code(201).send(); | ||||||
|  | |||||||
| @ -96,6 +96,7 @@ export interface DeleteSecret extends OnlyId { | |||||||
| } | } | ||||||
| export interface SaveStorage extends OnlyId { | export interface SaveStorage extends OnlyId { | ||||||
| 	Body: { | 	Body: { | ||||||
|  | 		hostPath?: string; | ||||||
| 		path: string; | 		path: string; | ||||||
| 		newStorage: boolean; | 		newStorage: boolean; | ||||||
| 		storageId: string; | 		storageId: string; | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ | |||||||
| 	import { errorNotification } from '$lib/common'; | 	import { errorNotification } from '$lib/common'; | ||||||
| 	import { addToast } from '$lib/store'; | 	import { addToast } from '$lib/store'; | ||||||
| 	import CopyVolumeField from '$lib/components/CopyVolumeField.svelte'; | 	import CopyVolumeField from '$lib/components/CopyVolumeField.svelte'; | ||||||
|  | 	import SimpleExplainer from '$lib/components/SimpleExplainer.svelte'; | ||||||
| 	const { id } = $page.params; | 	const { id } = $page.params; | ||||||
| 	let isHttps = browser && window.location.protocol === 'https:'; | 	let isHttps = browser && window.location.protocol === 'https:'; | ||||||
| 	export let value: string; | 	export let value: string; | ||||||
| @ -33,11 +34,13 @@ | |||||||
| 			storage.path.replace(/\/\//g, '/'); | 			storage.path.replace(/\/\//g, '/'); | ||||||
| 			await post(`/applications/${id}/storages`, { | 			await post(`/applications/${id}/storages`, { | ||||||
| 				path: storage.path, | 				path: storage.path, | ||||||
|  | 				hostPath: storage.hostPath, | ||||||
| 				storageId: storage.id, | 				storageId: storage.id, | ||||||
| 				newStorage | 				newStorage | ||||||
| 			}); | 			}); | ||||||
| 			dispatch('refresh'); | 			dispatch('refresh'); | ||||||
| 			if (isNew) { | 			if (isNew) { | ||||||
|  | 				storage.hostPath = null; | ||||||
| 				storage.path = null; | 				storage.path = null; | ||||||
| 				storage.id = null; | 				storage.id = null; | ||||||
| 			} | 			} | ||||||
| @ -80,27 +83,42 @@ | |||||||
| 		<div class="flex gap-4 pb-2" class:pt-8={isNew}> | 		<div class="flex gap-4 pb-2" class:pt-8={isNew}> | ||||||
| 			{#if storage.applicationId} | 			{#if storage.applicationId} | ||||||
| 				{#if storage.oldPath} | 				{#if storage.oldPath} | ||||||
| 		 |  | ||||||
| 					<CopyVolumeField | 					<CopyVolumeField | ||||||
| 						value="{storage.applicationId}{storage.path.replace(/\//gi, '-').replace('-app', '')}" | 						value="{storage.applicationId}{storage.path.replace(/\//gi, '-').replace('-app', '')}" | ||||||
| 					/> | 					/> | ||||||
| 				{:else} | 				{:else if !storage.hostPath} | ||||||
| 				 |  | ||||||
| 					<CopyVolumeField | 					<CopyVolumeField | ||||||
| 						value="{storage.applicationId}{storage.path.replace(/\//gi, '-').replace('-app', '')}" | 						value="{storage.applicationId}{storage.path.replace(/\//gi, '-').replace('-app', '')}" | ||||||
| 					/> | 					/> | ||||||
| 				{/if} | 				{/if} | ||||||
| 			{/if} | 			{/if} | ||||||
|  | 
 | ||||||
|  | 			{#if isNew} | ||||||
|  | 				<div class="w-full"> | ||||||
|  | 					<input | ||||||
|  | 						disabled={!isNew} | ||||||
|  | 						readonly={!isNew} | ||||||
|  | 						bind:value={storage.hostPath} | ||||||
|  | 						placeholder="Host path, example: ~/.directory" | ||||||
|  | 					/> | ||||||
|  | 
 | ||||||
|  | 					<SimpleExplainer | ||||||
|  | 						text="You can mount <span class='text-yellow-400 font-bold'>host paths</span> from the operating system.<br>Leave it empty to define a volume based volume." | ||||||
|  | 					/> | ||||||
|  | 				</div> | ||||||
|  | 			{:else if storage.hostPath} | ||||||
|  | 				<input disabled readonly value={storage.hostPath} /> | ||||||
|  | 			{/if} | ||||||
| 			<input | 			<input | ||||||
| 				disabled={!isNew} | 				disabled={!isNew} | ||||||
| 				readonly={!isNew} | 				readonly={!isNew} | ||||||
| 				class="w-full" | 				class="w-full" | ||||||
| 				bind:value={storage.path} | 				bind:value={storage.path} | ||||||
| 				required | 				required | ||||||
| 				placeholder="eg: /data" | 				placeholder="Mount point inside the container, example: /data" | ||||||
| 			/> | 			/> | ||||||
| 
 | 
 | ||||||
| 			<div class="flex items-center justify-center"> | 			<div class="flex items-start justify-center"> | ||||||
| 				{#if isNew} | 				{#if isNew} | ||||||
| 					<div class="w-full lg:w-64"> | 					<div class="w-full lg:w-64"> | ||||||
| 						<button class="btn btn-sm btn-primary w-full" on:click={() => saveStorage(true)} | 						<button class="btn btn-sm btn-primary w-full" on:click={() => saveStorage(true)} | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user