Ohne SSH zum Node kommt der Guest-Agent jetzt über eine feste Bau-Adresse ins Template: cloud-init setzt Adresse und den Bereitstellungsschlüssel für den Standardbenutzer, ProxDeploy meldet sich per SSH in der VM an, installiert den Agent und entfernt danach Schlüssel, Adresse und cloud-init-Angaben. Dabei korrigiert: der Verbindungstest meldete „Template-Bau per API“ auch ohne Weg, den Agent einzubauen. Im Protokoll erscheinen unveränderte JSON-Felder nicht mehr als geändert. Refs #021 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
174 lines
5.5 KiB
TypeScript
174 lines
5.5 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
import {
|
||
benoetigteRechte,
|
||
faehigkeitenAbleiten,
|
||
rechtePruefen,
|
||
versionLesen,
|
||
type StorageInfo,
|
||
} from "@/engine/proxmox/faehigkeiten";
|
||
|
||
const v = (text: string) => versionLesen(text)!;
|
||
const storage = (name: string, inhalte: string[]): StorageInfo => ({
|
||
storage: name,
|
||
typ: "dir",
|
||
inhalte,
|
||
aktiv: true,
|
||
geteilt: false,
|
||
frei: null,
|
||
gesamt: null,
|
||
});
|
||
|
||
describe("PVE-Version", () => {
|
||
it("liest übliche Schreibweisen", () => {
|
||
expect(v("9.2.1")).toMatchObject({ major: 9, minor: 2, patch: 1 });
|
||
expect(v("8.4")).toMatchObject({ major: 8, minor: 4, patch: 0 });
|
||
expect(versionLesen("kaputt")).toBeNull();
|
||
});
|
||
});
|
||
|
||
describe("Rolle ProxDeploy je Version", () => {
|
||
const namen = (text: string) =>
|
||
benoetigteRechte(v(text))
|
||
.filter((r) => r.pflicht)
|
||
.map((r) => r.recht);
|
||
|
||
it("PVE 9 nutzt die Guest-Agent-Rechte statt VM.Monitor", () => {
|
||
expect(namen("9.2.1")).toEqual(
|
||
expect.arrayContaining(["VM.GuestAgent.Audit", "VM.GuestAgent.Unrestricted"]),
|
||
);
|
||
expect(namen("9.2.1")).not.toContain("VM.Monitor");
|
||
});
|
||
|
||
it("PVE 8 braucht VM.Monitor für den Guest-Agent", () => {
|
||
expect(namen("8.4.1")).toContain("VM.Monitor");
|
||
expect(namen("8.4.1")).not.toContain("VM.GuestAgent.Unrestricted");
|
||
});
|
||
|
||
it("download-url: Sys.AccessNetwork ab 8.2, davor Sys.Modify", () => {
|
||
expect(namen("8.2.0")).toContain("Sys.AccessNetwork");
|
||
expect(namen("8.1.4")).toContain("Sys.Modify");
|
||
expect(namen("8.1.4")).not.toContain("Sys.AccessNetwork");
|
||
});
|
||
});
|
||
|
||
describe("Rechteprüfung", () => {
|
||
const version = v("9.2.1");
|
||
const voll = Object.fromEntries(benoetigteRechte(version).map((r) => [r.recht, 1]));
|
||
|
||
it("meldet nichts, wenn alles da ist", () => {
|
||
const rechte = {
|
||
"/": voll,
|
||
"/vms": voll,
|
||
"/sdn/zones/localnetwork": voll,
|
||
"/nodes/pve01": voll,
|
||
"/storage/local-zfs": voll,
|
||
};
|
||
expect(rechtePruefen(version, rechte, { storages: ["local-zfs"], nodes: ["pve01"] })).toEqual([]);
|
||
});
|
||
|
||
it("nennt fehlende Rechte mit Pfad und Zweck", () => {
|
||
const ohneAgent = { ...voll, "VM.GuestAgent.Unrestricted": 0 };
|
||
const rechte = {
|
||
"/": voll,
|
||
"/vms": ohneAgent,
|
||
"/sdn/zones/localnetwork": voll,
|
||
"/nodes/pve01": voll,
|
||
"/storage/local-zfs": voll,
|
||
};
|
||
expect(rechtePruefen(version, rechte, { storages: ["local-zfs"], nodes: ["pve01"] })).toEqual([
|
||
expect.objectContaining({ recht: "VM.GuestAgent.Unrestricted", pfad: "/vms", pflicht: true }),
|
||
]);
|
||
});
|
||
|
||
it("Storage-Rechte: einer reicht; Node-Rechte: jeder Node braucht sie", () => {
|
||
const rechte = {
|
||
"/": voll,
|
||
"/vms": voll,
|
||
"/sdn/zones/localnetwork": voll,
|
||
"/nodes/pve01": voll,
|
||
"/nodes/pve02": {},
|
||
"/storage/a": voll,
|
||
"/storage/b": {},
|
||
};
|
||
const fehlend = rechtePruefen(version, rechte, { storages: ["a", "b"], nodes: ["pve01", "pve02"] });
|
||
expect(fehlend.filter((f) => f.pflicht).map((f) => `${f.recht} ${f.pfad}`)).toEqual([
|
||
"Sys.AccessNetwork /nodes/pve02",
|
||
]);
|
||
});
|
||
});
|
||
|
||
describe("Strategien", () => {
|
||
const mitImport = [storage("local", ["iso", "import", "snippets"])];
|
||
const ohneImport = [storage("local", ["iso", "snippets"])];
|
||
|
||
it("PVE 9.2 mit import-Storage ohne SSH: Image ginge per API, der Guest-Agent aber nicht – außer mit Bau-Adresse", () => {
|
||
const ohne = faehigkeitenAbleiten({
|
||
version: v("9.2.1"),
|
||
storages: mitImport,
|
||
ssh: "aus",
|
||
rechteFehlen: false,
|
||
});
|
||
expect(ohne.templateBau.weg).toBe("nicht_moeglich");
|
||
expect(ohne.templateBau.grund).toMatch(/Bau-Adresse/);
|
||
const mit = faehigkeitenAbleiten({
|
||
version: v("9.2.1"),
|
||
storages: mitImport,
|
||
ssh: "aus",
|
||
rechteFehlen: false,
|
||
bauNetz: true,
|
||
});
|
||
expect(mit.templateBau).toMatchObject({ weg: "api" });
|
||
expect(mit.templateBau.grund).toMatch(/Bau-Adresse/);
|
||
expect(mit.vmProvisionierung.weg).toBe("api");
|
||
expect(mit.lxcProvisionierung).toMatchObject({ weg: "ssh", ueber: "gast" });
|
||
expect(mit.snippets.weg).toBe("nicht_moeglich");
|
||
});
|
||
|
||
it("PVE 8.3: Template-Bau nur per SSH; ohne SSH mit Begründung unmöglich", () => {
|
||
expect(
|
||
faehigkeitenAbleiten({ version: v("8.3.5"), storages: mitImport, ssh: "ok", rechteFehlen: false })
|
||
.templateBau.weg,
|
||
).toBe("ssh");
|
||
const ohne = faehigkeitenAbleiten({
|
||
version: v("8.3.5"),
|
||
storages: mitImport,
|
||
ssh: "aus",
|
||
rechteFehlen: false,
|
||
});
|
||
expect(ohne.templateBau).toMatchObject({ weg: "nicht_moeglich" });
|
||
expect(ohne.templateBau.grund).toContain("8.4");
|
||
});
|
||
|
||
it("PVE 8.4 ohne import-Storage nennt den Ausweg", () => {
|
||
const f = faehigkeitenAbleiten({
|
||
version: v("8.4.1"),
|
||
storages: ohneImport,
|
||
ssh: "aus",
|
||
rechteFehlen: false,
|
||
});
|
||
expect(f.templateBau.grund).toMatch(/Inhaltstyp „Import“/);
|
||
});
|
||
|
||
it("mit SSH: Snippets, pct exec und erweiterte LXC-Features", () => {
|
||
const f = faehigkeitenAbleiten({
|
||
version: v("9.2.1"),
|
||
storages: mitImport,
|
||
ssh: "ok",
|
||
rechteFehlen: false,
|
||
});
|
||
expect(f.snippets.weg).toBe("ssh");
|
||
expect(f.lxcProvisionierung).toMatchObject({ ueber: "node" });
|
||
expect(f.lxcFeaturesErweitert.weg).toBe("ssh");
|
||
});
|
||
|
||
it("unbestätigter Host-Key zählt nicht als SSH", () => {
|
||
const f = faehigkeitenAbleiten({
|
||
version: v("8.3.5"),
|
||
storages: mitImport,
|
||
ssh: "nicht_bestaetigt",
|
||
rechteFehlen: false,
|
||
});
|
||
expect(f.templateBau.weg).toBe("nicht_moeglich");
|
||
});
|
||
});
|