feat(deploy): conflict-free 'git pull' update flow (D001/D004/D005)
- 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.
This commit is contained in:
1 parent
4960eef312
commit
5d81f617f0
6 files changed
+305
No files matched your search
@@ -0,0 +1,57 @@
|
||||
#!/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()
|
||||
Reference in new issue
Block a user