Compare commits

...

1 Commits

Author SHA1 Message Date
Deeman
511a0ebac7 fix(supervisor): always deploy web app on new tag
All checks were successful
CI / test (push) Successful in 1m0s
CI / tag (push) Successful in 3s
The previous approach diffed HEAD~1 vs HEAD to detect web/ changes,
but this missed changes inside merge commits (HEAD~1 IS the merge,
so the diff only saw the follow-up CHANGELOG commit). Result: web
containers never got rebuilt after merge-based pushes.

Simpler and deterministic: always run deploy.sh on every new tag.
Blue/green swap is zero-downtime and Docker layer caching makes
no-op builds fast (~10s). Removes web_code_changed() entirely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 11:48:07 +01:00

View File

@@ -267,48 +267,6 @@ def run_export() -> None:
send_alert(f"[export] {err}") send_alert(f"[export] {err}")
_last_seen_head: str | None = None
def web_code_changed() -> bool:
"""True on the first tick after a commit that changed web app code or secrets.
Compares the current HEAD to the HEAD from the previous tick. On first call
after process start (e.g. after os.execv reloads new code), falls back to
HEAD~1 so the just-deployed commit is evaluated exactly once.
Records HEAD before returning so the same commit never triggers twice.
"""
global _last_seen_head
result = subprocess.run(
["git", "rev-parse", "HEAD"], capture_output=True, text=True, timeout=10,
)
if result.returncode != 0:
return False
current_head = result.stdout.strip()
if _last_seen_head is None:
# Fresh process — use HEAD~1 as base (evaluates the newly deployed tag).
base_result = subprocess.run(
["git", "rev-parse", "HEAD~1"], capture_output=True, text=True, timeout=10,
)
base = base_result.stdout.strip() if base_result.returncode == 0 else current_head
else:
base = _last_seen_head
_last_seen_head = current_head # advance now — won't fire again for this HEAD
if base == current_head:
return False
diff = subprocess.run(
["git", "diff", "--name-only", base, current_head, "--",
"web/", "Dockerfile", ".env.prod.sops"],
capture_output=True, text=True, timeout=30,
)
return bool(diff.stdout.strip())
def current_deployed_tag() -> str | None: def current_deployed_tag() -> str | None:
"""Return the highest-version tag pointing at HEAD, or None. """Return the highest-version tag pointing at HEAD, or None.
@@ -360,6 +318,15 @@ def git_pull_and_sync() -> None:
run_shell("uv sync --all-packages") run_shell("uv sync --all-packages")
# Apply any model changes (FULL→INCREMENTAL, new models, etc.) before re-exec # Apply any model changes (FULL→INCREMENTAL, new models, etc.) before re-exec
run_shell("uv run sqlmesh -p transform/sqlmesh_padelnomics plan prod --auto-apply") run_shell("uv run sqlmesh -p transform/sqlmesh_padelnomics plan prod --auto-apply")
# Always redeploy the web app on new tag — blue/green swap is zero-downtime
# and Docker layer caching makes no-op builds fast. Previous approach of
# diffing HEAD~1 missed changes inside merge commits.
logger.info("Deploying web app (blue/green swap)")
ok, err = run_shell("./deploy.sh")
if ok:
send_alert(f"[deploy] {latest} ok")
else:
send_alert(f"[deploy] {latest} failed: {err}")
# Re-exec so the new code is loaded. os.execv replaces this process in-place; # Re-exec so the new code is loaded. os.execv replaces this process in-place;
# systemd sees it as the same PID and does not restart the unit. # systemd sees it as the same PID and does not restart the unit.
logger.info("Deploy complete — re-execing to load new code") logger.info("Deploy complete — re-execing to load new code")
@@ -408,14 +375,6 @@ def tick() -> None:
# Export serving tables # Export serving tables
run_export() run_export()
# Deploy web app if code changed
if os.getenv("SUPERVISOR_GIT_PULL") and web_code_changed():
logger.info("Web code changed — deploying")
ok, err = run_shell("./deploy.sh")
if ok:
send_alert("[deploy] ok")
else:
send_alert(f"[deploy] failed: {err}")
finally: finally:
conn.close() conn.close()