Profile sind deklarative Schritte je Distributionsfamilie; der Adapter übersetzt sie in ein idempotentes Shell-Skript. Ein neuer Adapter (rhel, alpine) ist eine Datei und ein Eintrag in der Registry. Variablen werden typisiert geprüft, in run-Schritten jeder Wert für die Shell gequotet. SSH-Härtung läuft zuletzt, damit sich eine Provisionierung per SSH nicht selbst aussperrt. Geprüft mit shellcheck und echt in Containern (Debian 12/13, Ubuntu 22.04/24.04/26.04) inklusive zweitem Lauf. Dabei aufgefallen: frische Images haben keine Paketlisten – der Vorspann macht jetzt apt-get update. Closes #005 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
56 lines
2.2 KiB
TypeScript
56 lines
2.2 KiB
TypeScript
import { execFileSync } from "node:child_process";
|
||
import { mkdtempSync, writeFileSync } from "node:fs";
|
||
import { tmpdir } from "node:os";
|
||
import { join } from "node:path";
|
||
import { describe, expect, it } from "vitest";
|
||
import { debianAdapter } from "@/engine/adapter/debian";
|
||
import { katalogLaden } from "@/engine/katalog/laden";
|
||
import { profileAufloesen } from "@/engine/profile/aufloesen";
|
||
import { profileRendern } from "@/engine/profile/rendern";
|
||
|
||
/** shellcheck über Docker – läuft nur, wenn Docker und das Image da sind, und sagt das sonst. */
|
||
function shellcheckVerfuegbar(): boolean {
|
||
try {
|
||
execFileSync("docker", ["image", "inspect", "koalaman/shellcheck:stable"], { stdio: "ignore" });
|
||
return true;
|
||
} catch {
|
||
console.warn("shellcheck übersprungen: docker oder koalaman/shellcheck:stable fehlt");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
describe.runIf(shellcheckVerfuegbar())("shellcheck", () => {
|
||
it("findet in den gerenderten Katalogprofilen nichts", async () => {
|
||
const katalog = await katalogLaden("catalog");
|
||
const profile = new Map(katalog.profile.map((p) => [p.eintrag.id, p.eintrag]));
|
||
const ordner = mkdtempSync(join(tmpdir(), "pd-sc-"));
|
||
for (const { eintrag: distro } of katalog.distributionen) {
|
||
const aufgeloest = profileAufloesen(["docker", "zabbix-agent"], profile, "debian", {
|
||
base: { fail2ban: true },
|
||
"zabbix-agent": { server: "zbx" },
|
||
});
|
||
if (!aufgeloest.ok) throw new Error("Auflösung fehlgeschlagen");
|
||
const { skript } = profileRendern(
|
||
aufgeloest.profile,
|
||
debianAdapter,
|
||
{ os: distro.os, gast: { name: "g", benutzer: "u" } },
|
||
[debianAdapter.benutzerAnlegen({ name: "u", sshSchluessel: ["ssh-ed25519 AAAA u"], sudo: true })],
|
||
);
|
||
writeFileSync(join(ordner, `${distro.id}.sh`), skript);
|
||
}
|
||
const ausgabe = (() => {
|
||
try {
|
||
execFileSync(
|
||
"bash",
|
||
["-c", `docker run --rm -v ${ordner}:/s:ro koalaman/shellcheck:stable -s bash /s/*.sh`],
|
||
{ stdio: "pipe" },
|
||
);
|
||
return "";
|
||
} catch (fehler) {
|
||
return String((fehler as { stdout?: Buffer }).stdout ?? fehler);
|
||
}
|
||
})();
|
||
expect(ausgabe).toBe("");
|
||
}, 60_000);
|
||
});
|