- deploy.sh: clean-tree guard + git fetch && git pull --ff-only + optional dep reinstall + app.db --migrate + restart. - restart-service.sh: supervisor-agnostic restart (systemd or fallback), waits for an in-flight scan. - check_runtime_git.sh: proves no scan writes git-tracked runtime data. - ids.service: systemd unit template. - tests/test_deployment.py: stdlib unittest invariant tests for the deployment runtime-isolation guarantee.
58 lines
2.1 KiB
Python
58 lines
2.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Deployment invariant tests (D001).
|
|
|
|
Proves that running a scan never creates or modifies git-tracked runtime data,
|
|
which is what makes ``git pull`` conflict-free on a deployed server.
|
|
|
|
Run with:
|
|
python -m unittest discover -s tests -p 'test_*.py'
|
|
or (if pytest is installed):
|
|
python -m pytest tests/
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import unittest
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
# The paloalto scanner-ips.txt seed is deliberately tracked (read-only); every
|
|
# other file under the runtime dirs must remain untracked.
|
|
ALLOWED_TRACKED_RUNTIME = {os.path.join("app", "data", "paloalto",
|
|
"scanner-ips.txt")}
|
|
|
|
|
|
def _git(*args: str) -> str:
|
|
return subprocess.run(["git", *args], cwd=ROOT, check=True,
|
|
capture_output=True, text=True).stdout
|
|
|
|
|
|
class RuntimeGitTest(unittest.TestCase):
|
|
def test_no_runtime_data_tracked(self):
|
|
"""No runtime file (other than the read-only paloalto seed) is tracked."""
|
|
tracked = _git("ls-files", "app/data", "app/reports").splitlines()
|
|
unexpected = [f for f in tracked
|
|
if f not in ALLOWED_TRACKED_RUNTIME]
|
|
self.assertEqual(
|
|
unexpected, [],
|
|
"git-tracked runtime files would make `git pull` conflict; "
|
|
"add them to .gitignore: %s" % unexpected)
|
|
|
|
def test_runtime_write_paths_ignored(self):
|
|
"""Every runtime write path is ignored by git (won't dirty the tree)."""
|
|
stats = subprocess.run(
|
|
["git", "check-ignore", "-v",
|
|
"app/data/scan_state.json", "app/data/scan_jobs.json",
|
|
"app/data/whois_cache.json", "app/data/ids.db",
|
|
"app/data/ids.db-wal", "app/reports/app/data/blocklist_ipv4.txt",
|
|
], cwd=ROOT, capture_output=True, text=True).stdout
|
|
self.assertIn("scan_state.json", stats)
|
|
self.assertIn("ids.db", stats)
|
|
self.assertIn("blocklist_ipv4.txt", stats)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|