mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-29 18:36:55 +00:00
feat(metablog-webhook): git_pull helper + deploy ring buffer (ref #113)
- git_pull(site_dir, branch) -> (old_sha, new_sha) via 4 git ops (rev-parse, fetch, reset, rev-parse) with timeouts (60s fetch, 10s local ops). - Ring buffer holds the last 50 deploy records; oldest evicted on overflow. list_deploys() returns newest-first. 5 new pytest cases cover the buffer and git ops (with subprocess stubbed via monkeypatch). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f1946fb178
commit
da0b4e537d
|
|
@ -74,3 +74,78 @@ def test_load_secret_caches(tmp_path, monkeypatch):
|
|||
p.write_bytes(b"changed\n")
|
||||
s2 = webhook.load_secret(p)
|
||||
assert s1 == s2 == b"first" # cache wins
|
||||
|
||||
|
||||
def test_record_deploy_appends():
|
||||
import webhook
|
||||
webhook._deploys.clear()
|
||||
webhook._record_deploy({"site": "a", "from": "x", "to": "y"})
|
||||
assert len(webhook._deploys) == 1
|
||||
assert webhook._deploys[0]["site"] == "a"
|
||||
|
||||
|
||||
def test_record_deploy_evicts_oldest_at_50():
|
||||
import webhook
|
||||
webhook._deploys.clear()
|
||||
for i in range(55):
|
||||
webhook._record_deploy({"site": f"s{i}"})
|
||||
assert len(webhook._deploys) == 50
|
||||
# oldest 5 are gone; first remaining is s5
|
||||
assert webhook._deploys[0]["site"] == "s5"
|
||||
assert webhook._deploys[-1]["site"] == "s54"
|
||||
|
||||
|
||||
def test_list_deploys_returns_reversed():
|
||||
import webhook
|
||||
webhook._deploys.clear()
|
||||
webhook._record_deploy({"site": "a"})
|
||||
webhook._record_deploy({"site": "b"})
|
||||
out = webhook.list_deploys()
|
||||
assert out["count"] == 2
|
||||
assert out["deploys"][0]["site"] == "b" # newest first
|
||||
assert out["deploys"][1]["site"] == "a"
|
||||
|
||||
|
||||
def test_git_pull_returns_old_and_new_sha(tmp_path, monkeypatch):
|
||||
import webhook
|
||||
site = tmp_path / "site"
|
||||
site.mkdir()
|
||||
(site / ".git").mkdir()
|
||||
|
||||
calls = []
|
||||
sha_iter = iter(["aaa1111", "bbb2222"])
|
||||
|
||||
def fake_run(cmd, **kwargs):
|
||||
calls.append(cmd)
|
||||
class R:
|
||||
stdout = next(sha_iter) + "\n" if "rev-parse" in cmd else ""
|
||||
stderr = ""
|
||||
returncode = 0
|
||||
return R()
|
||||
|
||||
monkeypatch.setattr(webhook.subprocess, "run", fake_run)
|
||||
old, new = webhook.git_pull(site, "main")
|
||||
assert old == "aaa1111"
|
||||
assert new == "bbb2222"
|
||||
# ensure the 4 expected git commands happened in order
|
||||
op_names = [c[3] for c in calls]
|
||||
assert op_names == ["rev-parse", "fetch", "reset", "rev-parse"]
|
||||
|
||||
|
||||
def test_git_pull_timeout_propagates(tmp_path, monkeypatch):
|
||||
import webhook
|
||||
import subprocess
|
||||
site = tmp_path / "site"
|
||||
site.mkdir()
|
||||
(site / ".git").mkdir()
|
||||
|
||||
def fake_run(cmd, **kwargs):
|
||||
if "fetch" in cmd:
|
||||
raise subprocess.TimeoutExpired(cmd, kwargs.get("timeout", 60))
|
||||
class R:
|
||||
stdout = "abc\n"; stderr = ""; returncode = 0
|
||||
return R()
|
||||
|
||||
monkeypatch.setattr(webhook.subprocess, "run", fake_run)
|
||||
with pytest.raises(subprocess.TimeoutExpired):
|
||||
webhook.git_pull(site, "main")
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ reload nginx if site.json:domain changed, and log to a ring buffer.
|
|||
import hmac
|
||||
import hashlib
|
||||
import logging
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
|
|
@ -45,3 +46,43 @@ def verify_signature(secret: bytes, body: bytes, signature_hex: str) -> bool:
|
|||
return hmac.compare_digest(expected, signature_hex)
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
|
||||
|
||||
GIT_FETCH_TIMEOUT = 60
|
||||
GIT_OP_TIMEOUT = 10
|
||||
|
||||
|
||||
def git_pull(site_dir: Path, branch: str) -> tuple[str, str]:
|
||||
"""Pull site_dir to origin/<branch>. Returns (old_sha, new_sha).
|
||||
|
||||
Raises subprocess.TimeoutExpired or CalledProcessError on git failure.
|
||||
"""
|
||||
def _git(*args: str, timeout: int = GIT_OP_TIMEOUT) -> str:
|
||||
result = subprocess.run(
|
||||
["git", "-C", str(site_dir), *args],
|
||||
capture_output=True, text=True, timeout=timeout, check=True,
|
||||
)
|
||||
return result.stdout.strip()
|
||||
|
||||
old = _git("rev-parse", "HEAD")
|
||||
_git("fetch", "--quiet", "origin", branch, timeout=GIT_FETCH_TIMEOUT)
|
||||
_git("reset", "--hard", f"origin/{branch}")
|
||||
new = _git("rev-parse", "HEAD")
|
||||
return old, new
|
||||
|
||||
|
||||
# ─── Deploy ring buffer ──────────────────────────────────────────
|
||||
_deploys: list[dict] = []
|
||||
_DEPLOYS_MAX = 50
|
||||
|
||||
|
||||
def _record_deploy(entry: dict) -> None:
|
||||
"""Append a deploy record; evict oldest beyond cap."""
|
||||
_deploys.append(entry)
|
||||
if len(_deploys) > _DEPLOYS_MAX:
|
||||
_deploys.pop(0)
|
||||
|
||||
|
||||
def list_deploys() -> dict:
|
||||
"""Return deploys newest-first."""
|
||||
return {"deploys": list(reversed(_deploys)), "count": len(_deploys)}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user