149 lines
5.3 KiB
Python
Executable File
149 lines
5.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Lightweight release checks for AssetManager.
|
|
|
|
The checks are intentionally read-only and do not require a running database.
|
|
A failure exits with status 1 so the script can be used in CI or a release job.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import compileall
|
|
import importlib.util
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
APP_DIR = ROOT / "app"
|
|
|
|
|
|
def fail(message: str) -> None:
|
|
print(f"[FAIL] {message}")
|
|
raise SystemExit(1)
|
|
|
|
|
|
def ok(message: str) -> None:
|
|
print(f"[ OK ] {message}")
|
|
|
|
|
|
def check_version() -> None:
|
|
path = APP_DIR / "version.py"
|
|
spec = importlib.util.spec_from_file_location("assetmanager_release_version", path)
|
|
if spec is None or spec.loader is None:
|
|
fail("app/version.py could not be loaded")
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
app_version = getattr(module, "APP_VERSION", None)
|
|
conventional = getattr(module, "__version__", None)
|
|
if not isinstance(app_version, str) or not app_version.strip():
|
|
fail("APP_VERSION is missing or empty")
|
|
if conventional != app_version:
|
|
fail("__version__ must be an alias of APP_VERSION")
|
|
version_file = (ROOT / "VERSION").read_text(encoding="utf-8").strip()
|
|
if version_file != app_version:
|
|
fail(f"VERSION ({version_file}) and app/version.py ({app_version}) differ")
|
|
readme = (ROOT / "README.md").read_text(encoding="utf-8")
|
|
if app_version not in readme:
|
|
fail(f"README.md does not contain current version {app_version}")
|
|
ok(f"Version exports are valid ({app_version})")
|
|
|
|
|
|
def check_compile() -> None:
|
|
if not compileall.compile_dir(str(APP_DIR), quiet=1, force=True):
|
|
fail("Python compilation failed")
|
|
ok("Python sources compile")
|
|
|
|
|
|
def check_translations() -> None:
|
|
script = ROOT / "tools" / "check_translations.py"
|
|
result = subprocess.run([sys.executable, str(script)], cwd=ROOT, text=True)
|
|
if result.returncode:
|
|
fail("Translation check failed")
|
|
ok("Translation check passed")
|
|
|
|
|
|
def check_templates() -> None:
|
|
try:
|
|
from jinja2 import Environment, FileSystemLoader
|
|
except ImportError as exc:
|
|
fail(f"Jinja2 is unavailable: {exc}")
|
|
template_dir = APP_DIR / "templates"
|
|
env = Environment(loader=FileSystemLoader(str(template_dir)))
|
|
failures: list[str] = []
|
|
for path in sorted(template_dir.rglob("*.html")):
|
|
name = path.relative_to(template_dir).as_posix()
|
|
try:
|
|
env.get_template(name)
|
|
except Exception as exc: # syntax/load validation
|
|
failures.append(f"{name}: {exc}")
|
|
if failures:
|
|
fail("Template validation failed:\n " + "\n ".join(failures))
|
|
ok("Templates load without syntax errors")
|
|
|
|
|
|
|
|
def check_job_definition_frontend() -> None:
|
|
form_path = APP_DIR / "templates" / "job_definition_form.html"
|
|
content = form_path.read_text(encoding="utf-8")
|
|
required_frontend = [
|
|
'id="insert-job-test-script"',
|
|
'id="job-parameters-json"',
|
|
'id="add-job-parameter"',
|
|
'job-enabled-top',
|
|
'jobdefs.own_script',
|
|
]
|
|
missing = [item for item in required_frontend if item not in content]
|
|
if missing:
|
|
fail("Job-definition frontend validation failed; missing: " + ", ".join(missing))
|
|
|
|
template_dir = APP_DIR / "templates" / "jobs"
|
|
required_templates = {
|
|
"powershell_wrapper.ps1": ["{{JobId}}", "{{CallbackUrl}}", "Eigene Befehle hier einfügen"],
|
|
"python_wrapper.py.txt": ["{{JobId}}", "{{CallbackUrl}}", "Eigene Befehle hier einfügen"],
|
|
"bash_wrapper.sh": ["{{JobId}}", "{{CallbackUrl}}", "${status}", "${code}"],
|
|
"cmd_wrapper.cmd": ["{{JobId}}", "{{CallbackUrl}}", "Eigene Befehle hier einfügen"],
|
|
"powershell_test.txt": ["{{AssetName}}", "{{Hostname}}"],
|
|
"python_test.txt": ["{{AssetName}}", "{{Hostname}}"],
|
|
"bash_test.txt": ["{{AssetName}}", "{{Hostname}}"],
|
|
"cmd_test.txt": ["{{AssetName}}", "{{Hostname}}"],
|
|
}
|
|
failures: list[str] = []
|
|
for filename, markers in required_templates.items():
|
|
path = template_dir / filename
|
|
if not path.is_file():
|
|
failures.append(f"missing file: {filename}")
|
|
continue
|
|
template_content = path.read_text(encoding="utf-8")
|
|
for marker in markers:
|
|
if marker not in template_content:
|
|
failures.append(f"{filename}: missing {marker}")
|
|
if failures:
|
|
fail("Job script template validation failed:\n " + "\n ".join(failures))
|
|
ok("Job-definition backend generator safeguards are present")
|
|
|
|
|
|
def check_fastapi_import() -> None:
|
|
# Import only. Do not start Uvicorn and do not connect to external services.
|
|
sys.path.insert(0, str(ROOT))
|
|
os.environ.setdefault("DATABASE_URL", "sqlite+pysqlite:///:memory:")
|
|
try:
|
|
from app.main import app # noqa: F401
|
|
except Exception as exc:
|
|
fail(f"FastAPI application import failed: {type(exc).__name__}: {exc}")
|
|
ok("FastAPI application imports")
|
|
|
|
|
|
def main() -> None:
|
|
print("AssetManager release validation")
|
|
check_version()
|
|
check_compile()
|
|
check_translations()
|
|
check_templates()
|
|
check_job_definition_frontend()
|
|
check_fastapi_import()
|
|
print("All release checks passed.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|