mirror of
https://github.com/chatmail/relay.git
synced 2026-05-20 12:58:04 +00:00
refactor: extract sdist build into util with lock-based idempotency
Move _build_chatmaild from deployers.py into util.py as build_chatmaild_sdist() with fcntl-based file locking so parallel deploys do not race on the sdist. The build is called once from run_cmd() before pyinfra starts; deployers.py now only calls get_chatmaild_sdist() to locate the pre-built archive. Add test_build_chatmaild_sdist and test_get_chatmaild_sdist_errors.
This commit is contained in:
@@ -34,6 +34,7 @@ from .sshexec import (
|
|||||||
resolve_host_from_ssh_config,
|
resolve_host_from_ssh_config,
|
||||||
resolve_key_from_ssh_config,
|
resolve_key_from_ssh_config,
|
||||||
)
|
)
|
||||||
|
from .util import build_chatmaild_sdist
|
||||||
from .www import main as webdev_main
|
from .www import main as webdev_main
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -120,6 +121,9 @@ def run_cmd(args, out):
|
|||||||
env["CHATMAIL_WEBSITE_ONLY"] = "True" if args.website_only else ""
|
env["CHATMAIL_WEBSITE_ONLY"] = "True" if args.website_only else ""
|
||||||
env["CHATMAIL_DISABLE_MAIL"] = "True" if args.disable_mail else ""
|
env["CHATMAIL_DISABLE_MAIL"] = "True" if args.disable_mail else ""
|
||||||
env["CHATMAIL_REQUIRE_IROH"] = "True" if require_iroh else ""
|
env["CHATMAIL_REQUIRE_IROH"] = "True" if require_iroh else ""
|
||||||
|
|
||||||
|
if not args.website_only:
|
||||||
|
build_chatmaild_sdist()
|
||||||
if not args.dns_check_disabled:
|
if not args.dns_check_disabled:
|
||||||
env["CHATMAIL_ADDR_V4"] = remote_data.get("A") or ""
|
env["CHATMAIL_ADDR_V4"] = remote_data.get("A") or ""
|
||||||
env["CHATMAIL_ADDR_V6"] = remote_data.get("AAAA") or ""
|
env["CHATMAIL_ADDR_V6"] = remote_data.get("AAAA") or ""
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ Chat Mail pyinfra deploy.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -18,7 +15,7 @@ from pyinfra.facts.systemd import SystemdEnabled
|
|||||||
from pyinfra.operations import apt, files, pip, server, systemd
|
from pyinfra.operations import apt, files, pip, server, systemd
|
||||||
|
|
||||||
from cmdeploy.cmdeploy import Out
|
from cmdeploy.cmdeploy import Out
|
||||||
from cmdeploy.util import get_version_string
|
from cmdeploy.util import get_chatmaild_sdist, get_version_string
|
||||||
|
|
||||||
from .acmetool import AcmetoolDeployer
|
from .acmetool import AcmetoolDeployer
|
||||||
from .basedeploy import (
|
from .basedeploy import (
|
||||||
@@ -55,20 +52,6 @@ class Port(FactBase):
|
|||||||
return output[0]
|
return output[0]
|
||||||
|
|
||||||
|
|
||||||
def _build_chatmaild(dist_dir) -> None:
|
|
||||||
dist_dir = Path(dist_dir).resolve()
|
|
||||||
if dist_dir.exists():
|
|
||||||
shutil.rmtree(dist_dir)
|
|
||||||
dist_dir.mkdir()
|
|
||||||
subprocess.check_output(
|
|
||||||
[sys.executable, "-m", "build", "-n"]
|
|
||||||
+ ["--sdist", "chatmaild", "--outdir", str(dist_dir)]
|
|
||||||
)
|
|
||||||
entries = list(dist_dir.iterdir())
|
|
||||||
assert len(entries) == 1
|
|
||||||
return entries[0]
|
|
||||||
|
|
||||||
|
|
||||||
def remove_legacy_artifacts():
|
def remove_legacy_artifacts():
|
||||||
if not has_systemd():
|
if not has_systemd():
|
||||||
return
|
return
|
||||||
@@ -84,7 +67,7 @@ def remove_legacy_artifacts():
|
|||||||
|
|
||||||
def _install_remote_venv_with_chatmaild() -> None:
|
def _install_remote_venv_with_chatmaild() -> None:
|
||||||
remove_legacy_artifacts()
|
remove_legacy_artifacts()
|
||||||
dist_file = _build_chatmaild(dist_dir=Path("chatmaild/dist"))
|
dist_file = get_chatmaild_sdist()
|
||||||
remote_base_dir = "/usr/local/lib/chatmaild"
|
remote_base_dir = "/usr/local/lib/chatmaild"
|
||||||
remote_dist_file = f"{remote_base_dir}/dist/{dist_file.name}"
|
remote_dist_file = f"{remote_base_dir}/dist/{dist_file.name}"
|
||||||
remote_venv_dir = f"{remote_base_dir}/venv"
|
remote_venv_dir = f"{remote_base_dir}/venv"
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
from cmdeploy.util import collapse, get_git_hash, get_version_string, shell
|
import pytest
|
||||||
|
|
||||||
|
from cmdeploy.util import (
|
||||||
|
build_chatmaild_sdist,
|
||||||
|
collapse,
|
||||||
|
get_chatmaild_sdist,
|
||||||
|
get_git_hash,
|
||||||
|
get_version_string,
|
||||||
|
shell,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_collapse():
|
def test_collapse():
|
||||||
@@ -51,3 +60,28 @@ def test_git_helpers_with_commits_and_diffs(tmp_path):
|
|||||||
new_hash = get_git_hash(root=tmp_path)
|
new_hash = get_git_hash(root=tmp_path)
|
||||||
assert new_hash != git_hash
|
assert new_hash != git_hash
|
||||||
assert get_version_string(root=tmp_path) == new_hash
|
assert get_version_string(root=tmp_path) == new_hash
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_chatmaild_sdist(tmp_path):
|
||||||
|
dist_dir = tmp_path / "dist"
|
||||||
|
|
||||||
|
# First call builds the sdist
|
||||||
|
result = build_chatmaild_sdist(dist_dir)
|
||||||
|
assert result.name.endswith(".tar.gz")
|
||||||
|
assert result.stat().st_size > 0
|
||||||
|
|
||||||
|
# Second call is idempotent — returns the same file, no rebuild
|
||||||
|
mtime = result.stat().st_mtime
|
||||||
|
result2 = build_chatmaild_sdist(dist_dir)
|
||||||
|
assert result2 == result
|
||||||
|
assert result2.stat().st_mtime == mtime
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_chatmaild_sdist_errors(tmp_path):
|
||||||
|
with pytest.raises(FileNotFoundError):
|
||||||
|
get_chatmaild_sdist(tmp_path / "nonexistent")
|
||||||
|
|
||||||
|
empty = tmp_path / "empty"
|
||||||
|
empty.mkdir()
|
||||||
|
with pytest.raises(FileNotFoundError):
|
||||||
|
get_chatmaild_sdist(empty)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
"""Shared utility functions for cmdeploy."""
|
"""Shared utility functions for cmdeploy."""
|
||||||
|
|
||||||
|
import fcntl
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -15,11 +17,11 @@ def collapse(text):
|
|||||||
|
|
||||||
Handy for writing shell commands across multiple lines::
|
Handy for writing shell commands across multiple lines::
|
||||||
|
|
||||||
cmd = collapse(f\"""
|
cmd = collapse(f\"\"\"
|
||||||
cmdeploy run
|
cmdeploy run
|
||||||
--config {ct.ini}
|
--config {ct.ini}
|
||||||
--ssh-host {ct.domain}
|
--ssh-host {ct.domain}
|
||||||
\""")
|
\"\"\")
|
||||||
"""
|
"""
|
||||||
return textwrap.dedent(text).replace("\n", " ").strip()
|
return textwrap.dedent(text).replace("\n", " ").strip()
|
||||||
|
|
||||||
@@ -67,3 +69,42 @@ def get_version_string(root=None):
|
|||||||
if git_diff:
|
if git_diff:
|
||||||
return f"{git_hash}\n{git_diff}"
|
return f"{git_hash}\n{git_diff}"
|
||||||
return git_hash
|
return git_hash
|
||||||
|
|
||||||
|
|
||||||
|
def _chatmaild_default_dist_dir():
|
||||||
|
return _project_root() / "chatmaild" / "dist"
|
||||||
|
|
||||||
|
|
||||||
|
def build_chatmaild_sdist(dist_dir=None):
|
||||||
|
"""Build the chatmaild sdist if not already present (idempotent, process-safe)."""
|
||||||
|
|
||||||
|
if dist_dir is None:
|
||||||
|
dist_dir = _chatmaild_default_dist_dir()
|
||||||
|
dist_dir = Path(dist_dir).resolve()
|
||||||
|
dist_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
lockfile = dist_dir.parent / ".dist.lock"
|
||||||
|
with open(lockfile, "w") as fh:
|
||||||
|
fcntl.flock(fh, fcntl.LOCK_EX)
|
||||||
|
existing = [p for p in dist_dir.iterdir() if p.suffix == ".gz"]
|
||||||
|
if existing:
|
||||||
|
return existing[0]
|
||||||
|
subprocess.check_output(
|
||||||
|
[sys.executable, "-m", "build", "-n"]
|
||||||
|
+ ["--sdist", "chatmaild", "--outdir", str(dist_dir)],
|
||||||
|
cwd=str(_project_root()),
|
||||||
|
)
|
||||||
|
return get_chatmaild_sdist(dist_dir)
|
||||||
|
|
||||||
|
|
||||||
|
def get_chatmaild_sdist(dist_dir=None):
|
||||||
|
"""Return the path to the pre-built chatmaild sdist."""
|
||||||
|
if dist_dir is None:
|
||||||
|
dist_dir = _chatmaild_default_dist_dir()
|
||||||
|
|
||||||
|
entries = list(Path(dist_dir).iterdir())
|
||||||
|
if len(entries) == 0:
|
||||||
|
raise FileNotFoundError(f"dist directory is empty: {dist_dir}")
|
||||||
|
if len(entries) > 1:
|
||||||
|
raise ValueError(f"expected one file in {dist_dir}, found {len(entries)}")
|
||||||
|
return entries[0]
|
||||||
|
|||||||
Reference in New Issue
Block a user