feat(templates): Profile ins Template einbacken
Beim Template-Bau lassen sich Profile wählen (nur solche ohne Pflichtvariablen). Nach dem Vorbereitungsstart laufen sie über den Guest-Agent; danach werden Maschinen-ID, SSH-Host-Keys, Docker-Engine-ID und apt-Cache entfernt. Ausrollen nimmt das Template mit den meisten passenden Profilen und überspringt, was schon eingebacken ist. Auf pve-demo: Provisionierung mit base+docker 141 s ohne, ~30 s mit Template. Dabei gefunden: docker nimmt den Benutzer per guest.user in die Gruppe auf – Profile mit guest.* laufen deshalb beim Deploy noch einmal (idempotent, schnell). Closes #022 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -300,3 +300,63 @@ describe("Template-Bau", () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Profile einbacken (#022)", () => {
|
||||
it("startet die VM, provisioniert über den Agent, räumt auf, fährt herunter – Name und Tags nennen die Profile", async () => {
|
||||
const { mock, client } = await aufbauen({ version: "9.2.1" });
|
||||
const dateien: string[] = [];
|
||||
let exec = 0;
|
||||
mock
|
||||
.route("POST", /\/agent\/ping$/, () => ({ data: { result: {} } }))
|
||||
.route("POST", /\/agent\/file-write$/, (a) => {
|
||||
dateien.push(Buffer.from(a.rumpf.get("content")!, "base64").toString("utf8"));
|
||||
return { data: null };
|
||||
})
|
||||
.route("POST", /\/agent\/exec$/, () => ({ data: { pid: ++exec } }))
|
||||
.route("GET", /\/agent\/exec-status$/, () => ({ data: { exited: 1, exitcode: 0 } }))
|
||||
.route("GET", /\/agent\/file-read$/, () => ({ data: { content: "" } }))
|
||||
.route("POST", /\/status\/shutdown$/, () => ({ data: UPID("qmshutdown") }));
|
||||
const katalog = await katalogLaden("catalog");
|
||||
const { profileAufloesen } = await import("@/engine/profile/aufloesen");
|
||||
const aufgeloest = profileAufloesen(
|
||||
["docker"],
|
||||
new Map(katalog.profile.map((p) => [p.eintrag.id, p.eintrag])),
|
||||
"debian",
|
||||
);
|
||||
if (!aufgeloest.ok) throw new Error("Profile");
|
||||
const ergebnis = await phasenAusfuehren(
|
||||
templateBauSchritte({
|
||||
client,
|
||||
distribution: await debian13(),
|
||||
profile: aufgeloest.profile,
|
||||
knoten: "pve01",
|
||||
storage: "local-zfs",
|
||||
bridge: "vmbr0",
|
||||
ssh: sshAufzeichnung().ssh,
|
||||
auftragKurzId: "t",
|
||||
holen: async () => `${HASH} debian-13-genericcloud-amd64.qcow2`,
|
||||
vmidReservieren: async () => 9002,
|
||||
warten: async () => {},
|
||||
}),
|
||||
stumm,
|
||||
);
|
||||
expect(ergebnis).toEqual({ ok: true });
|
||||
const anlegen = Object.fromEntries(
|
||||
mock.anfragen.find((a) => a.methode === "POST" && a.pfad === "/nodes/pve01/qemu")!.rumpf,
|
||||
);
|
||||
expect(anlegen).toMatchObject({
|
||||
name: "tpl-debian-13-docker",
|
||||
tags: "proxdeploy-template;debian-13;profil-base;profil-docker",
|
||||
});
|
||||
expect(dateien.join("")).toContain("docker-compose-plugin");
|
||||
expect(dateien.join("")).toContain("truncate -s 0 /etc/machine-id");
|
||||
expect(dateien.join("")).toContain("rm -f /etc/ssh/ssh_host_*");
|
||||
const reihenfolge = schreibend(mock.anfragen).filter((z) => /status\/(start|shutdown)|template$/.test(z));
|
||||
expect(reihenfolge).toEqual([
|
||||
"POST /nodes/pve01/qemu/9002/status/start",
|
||||
"POST /nodes/pve01/qemu/9002/status/start",
|
||||
"POST /nodes/pve01/qemu/9002/status/shutdown",
|
||||
"POST /nodes/pve01/qemu/9002/template",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -147,6 +147,7 @@ async function eingabe(client: ProxmoxClient, anfrageTeil: Record<string, unknow
|
||||
anfrage.profile,
|
||||
new Map(katalog.profile.map((p) => [p.eintrag.id, p.eintrag])),
|
||||
"debian",
|
||||
anfrage.variablen,
|
||||
);
|
||||
if (!aufgeloest.ok) throw new Error("Profile");
|
||||
const angelegt: Array<Record<string, unknown>> = [];
|
||||
@@ -292,3 +293,50 @@ describe("VM ausrollen", () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Templates mit eingebackenen Profilen (#022)", () => {
|
||||
async function mitTemplates(templates: Array<{ vmid: number; tags: string }>) {
|
||||
const { mock, client, dateien } = await aufbauen();
|
||||
mock.route("GET", "/cluster/resources", () => ({
|
||||
data: [
|
||||
{ type: "node", node: "pve01", status: "online", maxmem: 64e9, mem: 8e9 },
|
||||
...templates.map((t) => ({ type: "qemu", vmid: t.vmid, node: "pve01", template: 1, tags: t.tags })),
|
||||
],
|
||||
}));
|
||||
mock.route("POST", /^\/nodes\/pve01\/qemu\/\d+\/clone$/, () => ({ data: UPID("qmclone") }));
|
||||
return { mock, client, dateien };
|
||||
}
|
||||
|
||||
it("wählt das Template mit den meisten passenden Profilen, nie eins mit ungewählten", async () => {
|
||||
const { client } = await mitTemplates([
|
||||
{ vmid: 9000, tags: "proxdeploy-template;debian-13" },
|
||||
{ vmid: 9002, tags: "proxdeploy-template;debian-13;profil-base;profil-docker" },
|
||||
{ vmid: 9003, tags: "proxdeploy-template;debian-13;profil-base;profil-docker;profil-nginx" },
|
||||
]);
|
||||
const { e } = await eingabe(client);
|
||||
const stand: VmAusrollStand = {};
|
||||
expect(await phasenAusfuehren(vmAusrollSchritte(e, stand), stumm)).toEqual({ ok: true });
|
||||
expect(stand.template).toEqual({ vmid: 9002, knoten: "pve01", eingebacken: ["base", "docker"] });
|
||||
});
|
||||
|
||||
it("überspringt gastunabhängige eingebackene Profile; docker (nutzt guest.user) läuft noch einmal", async () => {
|
||||
const { client, dateien } = await mitTemplates([
|
||||
{ vmid: 9002, tags: "proxdeploy-template;debian-13;profil-base;profil-docker" },
|
||||
]);
|
||||
const { e } = await eingabe(client);
|
||||
await phasenAusfuehren(vmAusrollSchritte(e), stumm);
|
||||
const skript = [...dateien.values()].join("");
|
||||
expect(skript).not.toContain("::proxdeploy:: profil base");
|
||||
expect(skript).toContain("::proxdeploy:: profil docker");
|
||||
expect(skript).toContain("usermod -aG docker florian");
|
||||
});
|
||||
|
||||
it("weicht eine Variable vom Standard ab, läuft auch ein eingebackenes Profil", async () => {
|
||||
const { client, dateien } = await mitTemplates([
|
||||
{ vmid: 9002, tags: "proxdeploy-template;debian-13;profil-base" },
|
||||
]);
|
||||
const { e } = await eingabe(client, { profile: ["base"], variablen: { base: { zeitzone: "UTC" } } });
|
||||
await phasenAusfuehren(vmAusrollSchritte(e), stumm);
|
||||
expect([...dateien.values()].join("")).toContain("::proxdeploy:: profil base");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user