refactor(volumes): rework files volumes to be more simple and persistent

This commit is contained in:
Mauricio Siu 2024-07-21 18:55:57 -06:00
parent 2e79c7230f
commit 8454e4f781
11 changed files with 126 additions and 76 deletions

View File

@ -330,7 +330,7 @@ export const AddVolumes = ({
/>
</>
)}
{serviceType === "application" && (
{serviceType !== "compose" && (
<FormField
control={form.control}
name="mountPath"

View File

@ -73,8 +73,7 @@ export const ShowVolumes = ({ applicationId }: Props) => {
key={mount.mountId}
className="flex w-full flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-10 border rounded-lg p-4"
>
{/* <Package className="size-8 self-center text-muted-foreground" /> */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 flex-col gap-4 sm:gap-8">
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 flex-col gap-4 sm:gap-8">
<div className="flex flex-col gap-1">
<span className="font-medium">Mount Type</span>
<span className="text-sm text-muted-foreground">
@ -91,12 +90,21 @@ export const ShowVolumes = ({ applicationId }: Props) => {
)}
{mount.type === "file" && (
<>
<div className="flex flex-col gap-1">
<span className="font-medium">Content</span>
<span className="text-sm text-muted-foreground">
{mount.content}
</span>
</div>
<div className="flex flex-col gap-1">
<span className="font-medium">File Path</span>
<span className="text-sm text-muted-foreground">
{mount.filePath}
</span>
</div>
</>
)}
{mount.type === "bind" && (
<div className="flex flex-col gap-1">
@ -118,6 +126,7 @@ export const ShowVolumes = ({ applicationId }: Props) => {
mountId={mount.mountId}
type={mount.type}
refetch={refetch}
serviceType="application"
/>
<DeleteVolume mountId={mount.mountId} refetch={refetch} />
</div>

View File

@ -48,6 +48,7 @@ const mySchema = z.discriminatedUnion("type", [
.object({
type: z.literal("file"),
content: z.string().optional(),
filePath: z.string().min(1, "File path required"),
})
.merge(mountSchema),
]);
@ -58,9 +59,23 @@ interface Props {
mountId: string;
type: "bind" | "volume" | "file";
refetch: () => void;
serviceType:
| "application"
| "postgres"
| "redis"
| "mongo"
| "redis"
| "mysql"
| "mariadb"
| "compose";
}
export const UpdateVolume = ({ mountId, type, refetch }: Props) => {
export const UpdateVolume = ({
mountId,
type,
refetch,
serviceType,
}: Props) => {
const utils = api.useUtils();
const { data } = api.mounts.one.useQuery(
{
@ -103,6 +118,7 @@ export const UpdateVolume = ({ mountId, type, refetch }: Props) => {
form.reset({
content: data.content || "",
mountPath: data.mountPath,
filePath: data.filePath || "",
type: "file",
});
}
@ -141,6 +157,7 @@ export const UpdateVolume = ({ mountId, type, refetch }: Props) => {
content: data.content,
mountPath: data.mountPath,
type: data.type,
filePath: data.filePath,
mountId,
})
.then(() => {
@ -166,6 +183,11 @@ export const UpdateVolume = ({ mountId, type, refetch }: Props) => {
<DialogDescription>Update the mount</DialogDescription>
</DialogHeader>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
{type === "file" && (
<AlertBlock type="warning">
Updating the mount will recreate the file or directory.
</AlertBlock>
)}
<Form {...form}>
<form
@ -211,6 +233,7 @@ export const UpdateVolume = ({ mountId, type, refetch }: Props) => {
)}
{type === "file" && (
<>
<FormField
control={form.control}
name="content"
@ -230,13 +253,33 @@ export const UpdateVolume = ({ mountId, type, refetch }: Props) => {
</FormItem>
)}
/>
<FormField
control={form.control}
name="filePath"
render={({ field }) => (
<FormItem>
<FormLabel>File Path</FormLabel>
<FormControl>
<Input
disabled
placeholder="Name of the file"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</>
)}
{serviceType !== "compose" && (
<FormField
control={form.control}
name="mountPath"
render={({ field }) => (
<FormItem>
<FormLabel>Mount Path</FormLabel>
<FormLabel>Mount Path (In the container)</FormLabel>
<FormControl>
<Input placeholder="Mount Path" {...field} />
</FormControl>
@ -245,6 +288,7 @@ export const UpdateVolume = ({ mountId, type, refetch }: Props) => {
</FormItem>
)}
/>
)}
</div>
<DialogFooter>
<Button

View File

@ -126,6 +126,7 @@ export const ShowVolumesCompose = ({ composeId }: Props) => {
mountId={mount.mountId}
type={mount.type}
refetch={refetch}
serviceType="compose"
/>
<DeleteVolume mountId={mount.mountId} refetch={refetch} />
</div>

View File

@ -117,6 +117,7 @@ export const ShowVolumes = ({ mariadbId }: Props) => {
mountId={mount.mountId}
type={mount.type}
refetch={refetch}
serviceType="mariadb"
/>
<DeleteVolume mountId={mount.mountId} refetch={refetch} />
</div>

View File

@ -113,6 +113,7 @@ export const ShowVolumes = ({ mongoId }: Props) => {
mountId={mount.mountId}
type={mount.type}
refetch={refetch}
serviceType="mongo"
/>
<DeleteVolume mountId={mount.mountId} refetch={refetch} />
</div>

View File

@ -115,6 +115,7 @@ export const ShowVolumes = ({ mysqlId }: Props) => {
mountId={mount.mountId}
type={mount.type}
refetch={refetch}
serviceType="mysql"
/>
<DeleteVolume mountId={mount.mountId} refetch={refetch} />
</div>

View File

@ -118,6 +118,7 @@ export const ShowVolumes = ({ postgresId }: Props) => {
mountId={mount.mountId}
type={mount.type}
refetch={refetch}
serviceType="postgres"
/>
<DeleteVolume mountId={mount.mountId} refetch={refetch} />
</div>

View File

@ -115,6 +115,7 @@ export const ShowVolumes = ({ redisId }: Props) => {
mountId={mount.mountId}
type={mount.type}
refetch={refetch}
serviceType="redis"
/>
<DeleteVolume mountId={mount.mountId} refetch={refetch} />
</div>

View File

@ -9,7 +9,6 @@ import {
} from "@/server/db/schema";
import { createFile } from "@/server/utils/docker/utils";
import { removeFileOrDirectory } from "@/server/utils/filesystem/directory";
import { execAsync } from "@/server/utils/process/execAsync";
import { TRPCError } from "@trpc/server";
import { type SQL, eq, sql } from "drizzle-orm";
@ -71,10 +70,10 @@ export const createMount = async (input: typeof apiCreateMount._type) => {
export const createFileMount = async (mountId: string) => {
try {
const mount = await findMountById(mountId);
const baseFilePath = await getBaseFilesMountPath(mountId);
const baseFilePath = await getBaseFilesPath(mountId);
await createFile(baseFilePath, mount.filePath || "", mount.content || "");
} catch (error) {
console.log(error);
console.log(`Error to create the file mount: ${error}`);
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the mount",
@ -109,6 +108,7 @@ export const updateMount = async (
mountId: string,
mountData: Partial<Mount>,
) => {
return await db.transaction(async (transaction) => {
const mount = await db
.update(mounts)
.set({
@ -129,8 +129,8 @@ export const updateMount = async (
await deleteFileMount(mountId);
await createFileMount(mountId);
}
return mount;
});
};
export const findMountsByApplicationId = async (
@ -184,14 +184,15 @@ export const deleteMount = async (mountId: string) => {
export const deleteFileMount = async (mountId: string) => {
const mount = await findMountById(mountId);
const basePath = await getBaseFilesMountPath(mountId);
const fullPath = path.join(basePath, mount.filePath || "");
if (!mount.filePath) return;
const basePath = await getBaseFilesPath(mountId);
const fullPath = path.join(basePath, mount.filePath);
try {
await removeFileOrDirectory(fullPath);
} catch (error) {}
};
export const getBaseFilesMountPath = async (mountId: string) => {
export const getBaseFilesPath = async (mountId: string) => {
const mount = await findMountById(mountId);
let absoluteBasePath = path.resolve(APPLICATIONS_PATH);

View File

@ -1,7 +1,7 @@
import fs from "node:fs";
import path, { join } from "node:path";
import path from "node:path";
import type { Readable } from "node:stream";
import { APPLICATIONS_PATH, COMPOSE_PATH, docker } from "@/server/constants";
import { APPLICATIONS_PATH, docker } from "@/server/constants";
import type { ContainerInfo, ResourceRequirements } from "dockerode";
import { parse } from "dotenv";
import type { ApplicationNested } from "../builders";
@ -305,13 +305,13 @@ export const generateFileMounts = (
return mounts
.filter((mount) => mount.type === "file")
.map((mount) => {
const fileName = mount.mountPath.split("/").pop();
const fileName = mount.filePath;
const absoluteBasePath = path.resolve(APPLICATIONS_PATH);
const directory = path.join(absoluteBasePath, appName, "files");
const filePath = path.join(directory, fileName || "");
const sourcePath = path.join(directory, fileName || "");
return {
Type: "bind" as const,
Source: filePath,
Source: sourcePath,
Target: mount.mountPath,
};
});
@ -323,27 +323,17 @@ export const createFile = async (
content: string,
) => {
try {
// Unir outputPath con filePath
const fullPath = path.join(outputPath, filePath);
// Verificar si la ruta termina en separador (indica que es un directorio)
if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) {
fs.mkdirSync(fullPath, { recursive: true });
console.log(`Directorio creado: ${fullPath}`);
return;
}
// Para archivos, obtener el directorio del archivo
const directory = path.dirname(fullPath);
// Crear el directorio si no existe
fs.mkdirSync(directory, { recursive: true });
// Escribir el archivo
fs.writeFileSync(fullPath, content || "");
console.log(`Archivo creado: ${fullPath}`);
} catch (error) {
console.log(error);
throw error;
}
};