refactor: docker volume prefix

This commit is contained in:
Nicolas Meienberger
2025-09-27 14:49:22 +02:00
parent 88e310cc4f
commit aa32ea322d
8 changed files with 29 additions and 24 deletions

View File

@@ -16,17 +16,17 @@ export const DockerTabContent = ({ volume }: Props) => {
services: { services: {
nginx: { nginx: {
image: "nginx:latest", image: "nginx:latest",
volumes: [`${volume.name}:/path/in/container`], volumes: [`im-${volume.name}:/path/in/container`],
}, },
}, },
volumes: { volumes: {
[volume.name]: { [`im-${volume.name}`]: {
external: true, external: true,
}, },
}, },
}); });
const dockerRunCommand = `docker run -v ${volume.name}:/path/in/container nginx:latest`; const dockerRunCommand = `docker run -v im-${volume.name}:/path/in/container nginx:latest`;
const containersQuery = getContainersUsingVolumeOptions({ path: { name: volume.name } }); const containersQuery = getContainersUsingVolumeOptions({ path: { name: volume.name } });
const { data: containersData, isLoading, error } = useQuery(containersQuery); const { data: containersData, isLoading, error } = useQuery(containersQuery);

View File

@@ -41,6 +41,8 @@ export default function DetailsPage({ loaderData }: Route.ComponentProps) {
const { data } = useQuery({ const { data } = useQuery({
...getVolumeOptions({ path: { name: name ?? "" } }), ...getVolumeOptions({ path: { name: name ?? "" } }),
initialData: loaderData, initialData: loaderData,
refetchInterval: 10000,
refetchOnWindowFocus: true,
}); });
const deleteVol = useMutation({ const deleteVol = useMutation({

View File

@@ -48,6 +48,8 @@ export default function Home({ loaderData }: Route.ComponentProps) {
const { data } = useQuery({ const { data } = useQuery({
...listVolumesOptions(), ...listVolumesOptions(),
initialData: loaderData, initialData: loaderData,
refetchInterval: 10000,
refetchOnWindowFocus: true,
}); });
const filteredVolumes = const filteredVolumes =

View File

@@ -32,10 +32,10 @@ export const scalarDescriptor = Scalar({
const driver = new Hono().use(honoLogger()).route("/", driverController); const driver = new Hono().use(honoLogger()).route("/", driverController);
const app = new Hono() const app = new Hono()
.use(honoLogger()) .use(honoLogger())
.get("*", serveStatic({ root: "./assets/frontend" })) .get("/healthcheck", (c) => c.json({ status: "ok" }))
.get("healthcheck", (c) => c.json({ status: "ok" })) .route("/api/v1/volumes", volumeController)
.basePath("/api/v1") .get("/assets/*", serveStatic({ root: "./assets/frontend" }))
.route("/volumes", volumeController); .get("*", serveStatic({ path: "./assets/frontend/index.html" }));
app.get("/openapi.json", generalDescriptor(app)); app.get("/openapi.json", generalDescriptor(app));
app.get("/docs", scalarDescriptor); app.get("/docs", scalarDescriptor);

View File

@@ -1,6 +1,6 @@
import { Hono } from "hono"; import { Hono } from "hono";
import { VOLUME_MOUNT_BASE } from "~/core/constants";
import { volumeService } from "../volumes/volume.service"; import { volumeService } from "../volumes/volume.service";
import { config } from "../../core/config";
export const driverController = new Hono() export const driverController = new Hono()
.post("/VolumeDriver.Capabilities", (c) => { .post("/VolumeDriver.Capabilities", (c) => {
@@ -30,8 +30,9 @@ export const driverController = new Hono()
return c.json({ Err: "Volume name is required" }, 400); return c.json({ Err: "Volume name is required" }, 400);
} }
const volumeRoot = config.volumeRootHost; const volumeName = body.Name.replace(/^im-/, "");
const mountpoint = `${volumeRoot}/${body.Name}/_data`;
const mountpoint = `${VOLUME_MOUNT_BASE}/${volumeName}/_data`;
return c.json({ return c.json({
Mountpoint: mountpoint, Mountpoint: mountpoint,
@@ -54,13 +55,12 @@ export const driverController = new Hono()
return c.json({ Err: "Volume name is required" }, 400); return c.json({ Err: "Volume name is required" }, 400);
} }
const volumeRoot = config.volumeRootHost; const { volume } = await volumeService.getVolume(body.Name.replace(/^im-/, ""));
const { volume } = await volumeService.getVolume(body.Name);
return c.json({ return c.json({
Volume: { Volume: {
Name: volume.name, Name: `im-${volume.name}`,
Mountpoint: `${volumeRoot}/${volume.name}/_data`, Mountpoint: `${VOLUME_MOUNT_BASE}/${volume.name}/_data`,
Status: {}, Status: {},
}, },
Err: "", Err: "",
@@ -68,11 +68,10 @@ export const driverController = new Hono()
}) })
.post("/VolumeDriver.List", async (c) => { .post("/VolumeDriver.List", async (c) => {
const volumes = await volumeService.listVolumes(); const volumes = await volumeService.listVolumes();
const volumeRoot = config.volumeRootHost;
const res = volumes.map((volume) => ({ const res = volumes.map((volume) => ({
Name: volume.name, Name: `im-${volume.name}`,
Mountpoint: `${volumeRoot}/${volume.name}/_data`, Mountpoint: `${VOLUME_MOUNT_BASE}/${volume.name}/_data`,
Status: {}, Status: {},
})); }));

View File

@@ -6,7 +6,6 @@ import Docker from "dockerode";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { ConflictError, InternalServerError, NotFoundError } from "http-errors-enhanced"; import { ConflictError, InternalServerError, NotFoundError } from "http-errors-enhanced";
import slugify from "slugify"; import slugify from "slugify";
import { config } from "../../core/config";
import { VOLUME_MOUNT_BASE } from "../../core/constants"; import { VOLUME_MOUNT_BASE } from "../../core/constants";
import { db } from "../../db/db"; import { db } from "../../db/db";
import { volumesTable } from "../../db/schema"; import { volumesTable } from "../../db/schema";
@@ -32,14 +31,14 @@ const createVolume = async (name: string, backendConfig: BackendConfig) => {
throw new ConflictError("Volume already exists"); throw new ConflictError("Volume already exists");
} }
const volumePathHost = path.join(config.volumeRootHost); const volumePathHost = path.join(VOLUME_MOUNT_BASE);
const val = await db const val = await db
.insert(volumesTable) .insert(volumesTable)
.values({ .values({
name: slug, name: slug,
config: backendConfig, config: backendConfig,
path: path.join(volumePathHost, slug), path: path.join(volumePathHost, slug, "_data"),
type: backendConfig.backend, type: backendConfig.backend,
}) })
.returning(); .returning();
@@ -210,7 +209,7 @@ const getContainersUsingVolume = async (name: string) => {
const container = docker.getContainer(info.Id); const container = docker.getContainer(info.Id);
const inspect = await container.inspect(); const inspect = await container.inspect();
const mounts = inspect.Mounts || []; const mounts = inspect.Mounts || [];
const usesVolume = mounts.some((mount) => mount.Type === "volume" && mount.Name === name); const usesVolume = mounts.some((mount) => mount.Type === "volume" && mount.Name === `im-${volume.name}`);
if (usesVolume) { if (usesVolume) {
usingContainers.push({ usingContainers.push({
id: inspect.Id, id: inspect.Id,

View File

@@ -13,6 +13,9 @@
"noImplicitOverride": true, "noImplicitOverride": true,
"module": "preserve", "module": "preserve",
"noEmit": true, "noEmit": true,
"lib": ["es2022"] "lib": ["es2022"],
"paths": {
"~/*": ["./src/*"]
},
} }
} }

View File

@@ -7,8 +7,8 @@
"tsc": "turbo run tsc", "tsc": "turbo run tsc",
"build": "turbo build", "build": "turbo build",
"gen:api-client": "openapi-ts", "gen:api-client": "openapi-ts",
"start:dev": "docker rm ironmount && docker compose up --build ironmount-dev", "start:dev": "docker compose down && docker compose up --build ironmount-dev",
"start:prod": "docker rm ironmount && docker compose up --build ironmount-prod" "start:prod": "docker compose down && docker compose up --build ironmount-prod"
}, },
"workspaces": [ "workspaces": [
"apps/*", "apps/*",