feat: finish the docker plugin (B15)

This commit is contained in:
LukeGus committed 2026-09-24 11:58:20 -05:00
1 parent 0a3f05d99d
commit 993a91c899
167 files changed
+26299 -5136

No files matched your search

@@ -0,0 +1,197 @@
/**
* The Docker host settings migration.
*
* An upgrade must be lossless: every host with Docker on, and every host on
* Podman, must come through into the docker plugin's host settings, whatever
* shape the engine returned the flag in. Running it twice must not duplicate
* or clobber what it moved.
*/
import { beforeEach, describe, expect, it, vi } from "vitest";
const state = vi.hoisted(() => ({
pluginRows: [] as Array<{
pluginId: string;
scope: string;
scopeId: string | null;
key: string;
value: string | null;
}>,
hostRows: [] as Array<Record<string, unknown>>,
pluginInstalled: true,
columnsGone: false,
}));
vi.mock("../../database/repositories/factory.js", () => ({
createCurrentPluginRepository: () => ({
findById: async (id: string) =>
state.pluginInstalled && id === "docker" ? { id } : null,
}),
createCurrentPluginSettingsRepository: () => ({
getAll: async (_pluginId: string, scope: string, scopeId: string | null) =>
state.pluginRows.filter(
(row) => row.scope === scope && row.scopeId === scopeId,
),
set: async (
pluginId: string,
scope: string,
scopeId: string | null,
key: string,
value: string | null,
) => {
const existing = state.pluginRows.find(
(row) =>
row.scope === scope && row.scopeId === scopeId && row.key === key,
);
if (existing) {
existing.value = value;
return;
}
state.pluginRows.push({ pluginId, scope, scopeId, key, value });
},
}),
}));
vi.mock("../../utils/crypto-migration/raw-rows.js", () => ({
selectRows: async () => {
if (state.columnsGone) throw new Error("no such column: enable_docker");
return state.hostRows;
},
}));
vi.mock("../../utils/logger.js", () => ({
databaseLogger: { info: vi.fn(), warn: vi.fn() },
}));
import {
dockerSettingsFromRow,
runDockerSettingsMigration,
} from "../../utils/crypto-migration/docker-settings-migration.js";
const hostValues = (hostId: number) =>
Object.fromEntries(
state.pluginRows
.filter((row) => row.scope === "host" && row.scopeId === String(hostId))
.map((row) => [row.key, JSON.parse(row.value ?? "null")]),
);
beforeEach(() => {
state.pluginRows = [];
state.hostRows = [];
state.pluginInstalled = true;
state.columnsGone = false;
});
describe("dockerSettingsFromRow", () => {
it("reads SQLite integers, real booleans and strings", () => {
expect(
dockerSettingsFromRow({ id: 1, enable_docker: 1, docker_config: null }),
).toEqual({ enableDocker: true });
expect(
dockerSettingsFromRow({
id: 1,
enable_docker: true,
docker_config: null,
}),
).toEqual({ enableDocker: true });
expect(
dockerSettingsFromRow({ id: 1, enable_docker: 0, docker_config: null }),
).toEqual({});
expect(
dockerSettingsFromRow({
id: 1,
enable_docker: false,
docker_config: null,
}),
).toEqual({});
});
it("keeps a Podman runtime and ignores the default or broken config", () => {
expect(
dockerSettingsFromRow({
id: 1,
enable_docker: 1,
docker_config: '{"runtime":"podman"}',
}),
).toEqual({ enableDocker: true, containerRuntime: "podman" });
expect(
dockerSettingsFromRow({
id: 1,
enable_docker: 1,
docker_config: '{"runtime":"docker"}',
}),
).toEqual({ enableDocker: true });
expect(
dockerSettingsFromRow({
id: 1,
enable_docker: 1,
docker_config: "{oops",
}),
).toEqual({ enableDocker: true });
});
});
describe("runDockerSettingsMigration", () => {
it("moves every host's docker options and skips hosts with nothing set", async () => {
state.hostRows = [
{ id: 1, enable_docker: 1, docker_config: null },
{ id: 2, enable_docker: 1, docker_config: '{"runtime":"podman"}' },
{ id: 3, enable_docker: 0, docker_config: null },
];
const result = await runDockerSettingsMigration();
expect(result).toEqual({ hostsMoved: 2, hostsSkipped: 0 });
expect(hostValues(1)).toEqual({ enableDocker: true });
expect(hostValues(2)).toEqual({
enableDocker: true,
containerRuntime: "podman",
});
expect(hostValues(3)).toEqual({});
});
it("changes nothing when run twice", async () => {
state.hostRows = [
{ id: 1, enable_docker: 1, docker_config: '{"runtime":"podman"}' },
];
await runDockerSettingsMigration();
const before = JSON.stringify(state.pluginRows);
const second = await runDockerSettingsMigration();
expect(second).toEqual({ hostsMoved: 0, hostsSkipped: 1 });
expect(JSON.stringify(state.pluginRows)).toBe(before);
});
it("keeps a value already saved in the plugin", async () => {
state.pluginRows.push({
pluginId: "docker",
scope: "host",
scopeId: "1",
key: "enableDocker",
value: "false",
});
state.hostRows = [{ id: 1, enable_docker: 1, docker_config: null }];
await runDockerSettingsMigration();
expect(hostValues(1)).toEqual({ enableDocker: false });
});
it("does nothing before the plugin row exists or once the columns are gone", async () => {
state.hostRows = [{ id: 1, enable_docker: 1, docker_config: null }];
state.pluginInstalled = false;
expect(await runDockerSettingsMigration()).toEqual({
hostsMoved: 0,
hostsSkipped: 0,
});
state.pluginInstalled = true;
state.columnsGone = true;
expect(await runDockerSettingsMigration()).toEqual({
hostsMoved: 0,
hostsSkipped: 0,
});
expect(state.pluginRows).toEqual([]);
});
});