Compare commits
2 Commits
c0877548ec
...
eeb5879a46
| Author | SHA1 | Date | |
|---|---|---|---|
| eeb5879a46 | |||
| 4dea06b0c6 |
75
app.py
75
app.py
@ -8,6 +8,7 @@ import re
|
|||||||
import secrets
|
import secrets
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
|
import time
|
||||||
import unicodedata
|
import unicodedata
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
@ -46,10 +47,19 @@ MINT_ISO_PATH = str(Path(os.environ.get("TUXFLOTTE_MINT_ISO_PATH", "data/upstrea
|
|||||||
# zum eigenen Arbeitsverzeichnis.
|
# zum eigenen Arbeitsverzeichnis.
|
||||||
ISO_BUILD_OUTPUT_DIR = Path(os.environ.get("TUXFLOTTE_ISO_BUILD_OUTPUT_DIR", "data/iso-builds")).resolve()
|
ISO_BUILD_OUTPUT_DIR = Path(os.environ.get("TUXFLOTTE_ISO_BUILD_OUTPUT_DIR", "data/iso-builds")).resolve()
|
||||||
ISO_BUILD_TIMEOUT_SECONDS = 1800
|
ISO_BUILD_TIMEOUT_SECONDS = 1800
|
||||||
|
# ISO-Ablauf nach 7 Tagen (Migration 0021): ergaenzt die bestehende
|
||||||
|
# "neuer Build ersetzt alten"-Aufraeumung um eine reine Zeitdimension.
|
||||||
|
ISO_BUILD_EXPIRY_DAYS = 7
|
||||||
|
ISO_BUILD_EXPIRY_SWEEP_INTERVAL_SECONDS = 60 * 60
|
||||||
|
|
||||||
app = FastAPI(title="Provisioning Activation Server", version="0.1.0")
|
app = FastAPI(title="Provisioning Activation Server", version="0.1.0")
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_event("startup")
|
||||||
|
def start_iso_build_expiry_sweep() -> None:
|
||||||
|
threading.Thread(target=run_iso_build_expiry_sweep, daemon=True).start()
|
||||||
|
|
||||||
|
|
||||||
class ActivationRequest(BaseModel):
|
class ActivationRequest(BaseModel):
|
||||||
activation_code: str
|
activation_code: str
|
||||||
device_fingerprint: str
|
device_fingerprint: str
|
||||||
@ -2076,7 +2086,7 @@ def recharge_enrollment_session(session_id: str, additional_devices: int, expire
|
|||||||
|
|
||||||
def iso_build_row_to_dict(row) -> dict:
|
def iso_build_row_to_dict(row) -> dict:
|
||||||
(build_id, organization_id, status, output_filename, error_message,
|
(build_id, organization_id, status, output_filename, error_message,
|
||||||
created_at, started_at, finished_at) = row
|
created_at, started_at, finished_at, expires_at) = row
|
||||||
return {
|
return {
|
||||||
"id": str(build_id),
|
"id": str(build_id),
|
||||||
"organization_id": str(organization_id),
|
"organization_id": str(organization_id),
|
||||||
@ -2086,6 +2096,7 @@ def iso_build_row_to_dict(row) -> dict:
|
|||||||
"created_at": created_at.isoformat(),
|
"created_at": created_at.isoformat(),
|
||||||
"started_at": started_at.isoformat() if started_at else None,
|
"started_at": started_at.isoformat() if started_at else None,
|
||||||
"finished_at": finished_at.isoformat() if finished_at else None,
|
"finished_at": finished_at.isoformat() if finished_at else None,
|
||||||
|
"expires_at": expires_at.isoformat() if expires_at else None,
|
||||||
}
|
}
|
||||||
|
|
||||||
def fetch_iso_build(build_id: str):
|
def fetch_iso_build(build_id: str):
|
||||||
@ -2094,7 +2105,7 @@ def fetch_iso_build(build_id: str):
|
|||||||
cur.execute(
|
cur.execute(
|
||||||
"""
|
"""
|
||||||
SELECT id, organization_id, status, output_filename, error_message,
|
SELECT id, organization_id, status, output_filename, error_message,
|
||||||
created_at, started_at, finished_at
|
created_at, started_at, finished_at, expires_at
|
||||||
FROM iso_builds
|
FROM iso_builds
|
||||||
WHERE id = %s
|
WHERE id = %s
|
||||||
""",
|
""",
|
||||||
@ -2109,7 +2120,7 @@ def fetch_iso_builds(organization_id: str):
|
|||||||
cur.execute(
|
cur.execute(
|
||||||
"""
|
"""
|
||||||
SELECT id, organization_id, status, output_filename, error_message,
|
SELECT id, organization_id, status, output_filename, error_message,
|
||||||
created_at, started_at, finished_at
|
created_at, started_at, finished_at, expires_at
|
||||||
FROM iso_builds
|
FROM iso_builds
|
||||||
WHERE organization_id = %s
|
WHERE organization_id = %s
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
@ -2138,6 +2149,12 @@ def update_iso_build(
|
|||||||
mark_started: bool = False,
|
mark_started: bool = False,
|
||||||
mark_finished: bool = False,
|
mark_finished: bool = False,
|
||||||
):
|
):
|
||||||
|
# expires_at nur bei einem tatsaechlich erfolgreichen Abschluss setzen -
|
||||||
|
# ein "failed"-Build hat nichts zum Downloaden, das ablaufen koennte
|
||||||
|
# (dessen Zeile raeumt weiterhin ausschliesslich cleanup_old_iso_builds()
|
||||||
|
# beim naechsten Bau derselben Organisation auf).
|
||||||
|
setzt_expiry = mark_finished and status == "completed"
|
||||||
|
|
||||||
with get_database_connection() as conn:
|
with get_database_connection() as conn:
|
||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
cur.execute(
|
cur.execute(
|
||||||
@ -2148,6 +2165,7 @@ def update_iso_build(
|
|||||||
error_message = %s
|
error_message = %s
|
||||||
{", started_at = CURRENT_TIMESTAMP" if mark_started else ""}
|
{", started_at = CURRENT_TIMESTAMP" if mark_started else ""}
|
||||||
{", finished_at = CURRENT_TIMESTAMP" if mark_finished else ""}
|
{", finished_at = CURRENT_TIMESTAMP" if mark_finished else ""}
|
||||||
|
{f", expires_at = CURRENT_TIMESTAMP + INTERVAL '{ISO_BUILD_EXPIRY_DAYS} days'" if setzt_expiry else ""}
|
||||||
WHERE id = %s
|
WHERE id = %s
|
||||||
""",
|
""",
|
||||||
(status, output_filename, error_message, build_id),
|
(status, output_filename, error_message, build_id),
|
||||||
@ -2186,6 +2204,57 @@ def cleanup_old_iso_builds(organization_id: str, keep_build_id) -> None:
|
|||||||
(organization_id, keep_build_id),
|
(organization_id, keep_build_id),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def cleanup_expired_iso_builds() -> int:
|
||||||
|
"""
|
||||||
|
Zeitbasierte Ergaenzung zu cleanup_old_iso_builds(): loescht Datei +
|
||||||
|
Zeile jedes abgeschlossenen Builds, dessen expires_at in der
|
||||||
|
Vergangenheit liegt - unabhaengig davon, ob die Organisation je erneut
|
||||||
|
baut (die bisherige Logik greift nur beim naechsten Bau derselben Org).
|
||||||
|
Laeuft periodisch ueber run_iso_build_expiry_sweep(). Gibt die Anzahl
|
||||||
|
geloeschter Builds zurueck (fuer Logging/Tests).
|
||||||
|
"""
|
||||||
|
|
||||||
|
with get_database_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
SELECT id, output_filename FROM iso_builds
|
||||||
|
WHERE status = 'completed' AND expires_at IS NOT NULL AND expires_at < CURRENT_TIMESTAMP
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
abgelaufene_builds = cur.fetchall()
|
||||||
|
|
||||||
|
for build_id, output_filename in abgelaufene_builds:
|
||||||
|
if output_filename:
|
||||||
|
(ISO_BUILD_OUTPUT_DIR / output_filename).unlink(missing_ok=True)
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
DELETE FROM iso_builds
|
||||||
|
WHERE status = 'completed' AND expires_at IS NOT NULL AND expires_at < CURRENT_TIMESTAMP
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
return len(abgelaufene_builds)
|
||||||
|
|
||||||
|
def run_iso_build_expiry_sweep() -> None:
|
||||||
|
"""
|
||||||
|
Endlosschleife in einem Daemon-Thread (siehe on_startup unten) - stuendlich
|
||||||
|
abgelaufene ISO-Builds raeumen. Ein Fehler in einem Durchlauf (z.B.
|
||||||
|
kurzzeitig nicht erreichbare DB) darf die Schleife nicht beenden, sonst
|
||||||
|
liefe der Sweep fuer den Rest der Prozesslaufzeit gar nicht mehr.
|
||||||
|
"""
|
||||||
|
|
||||||
|
while True:
|
||||||
|
time.sleep(ISO_BUILD_EXPIRY_SWEEP_INTERVAL_SECONDS)
|
||||||
|
|
||||||
|
try:
|
||||||
|
anzahl = cleanup_expired_iso_builds()
|
||||||
|
if anzahl:
|
||||||
|
print(f"[iso-build-expiry] {anzahl} abgelaufene(n) ISO-Build(s) geloescht.")
|
||||||
|
except Exception as exc:
|
||||||
|
print(f"[iso-build-expiry] Sweep-Fehler (naechster Versuch in {ISO_BUILD_EXPIRY_SWEEP_INTERVAL_SECONDS}s): {exc}")
|
||||||
|
|
||||||
def compute_iso_volid(organization_name: str) -> str:
|
def compute_iso_volid(organization_name: str) -> str:
|
||||||
"""
|
"""
|
||||||
ISO9660-Volume-ID fuer eine personalisierte Kunden-ISO: Organisationsname
|
ISO9660-Volume-ID fuer eine personalisierte Kunden-ISO: Organisationsname
|
||||||
|
|||||||
5
migrations/0021_iso_build_expiry.sql
Normal file
5
migrations/0021_iso_build_expiry.sql
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
-- ISO-Ablauf nach 7 Tagen (siehe ADR-Nachtrag zu ADR-0014). Ergaenzt die
|
||||||
|
-- bestehende "neuer Build ersetzt alten Build derselben Org"-Aufraeumung
|
||||||
|
-- (cleanup_old_iso_builds()) um eine reine Zeitdimension, die unabhaengig
|
||||||
|
-- davon greift, ob die Organisation je erneut baut.
|
||||||
|
ALTER TABLE iso_builds ADD COLUMN expires_at TIMESTAMPTZ;
|
||||||
Loading…
x
Reference in New Issue
Block a user