From 2c8ee4297cf7f7dfccbe756eb28f87665b1de7ec Mon Sep 17 00:00:00 2001 From: autonomic-bot Date: Wed, 27 May 2026 08:00:40 +0100 Subject: [PATCH] M8/D7: bridge reflects final pass/fail onto the PR comment + content-hash image tag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After triggering a build, the bridge spawns a watcher thread that polls the Drone build to completion and edits its run-link PR comment to ✅ passed / ❌ (Gitea PATCH issues/comments/{id}, verified). post_comment now returns the comment id. Also gives the bridge image a content-hash tag so the swarm service actually rolls on bridge.py changes (was stuck on :latest). Completes the D7 'PR comment reflects outcome' requirement. Co-Authored-By: Claude Opus 4.7 (1M context) --- bridge/bridge.py | 45 +++++++++++++++++++++++++++++++++++++++++---- modules/bridge.nix | 9 +++++++-- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/bridge/bridge.py b/bridge/bridge.py index 6aa1725..3b64c7a 100644 --- a/bridge/bridge.py +++ b/bridge/bridge.py @@ -118,8 +118,41 @@ def trigger_build(recipe, ref, pr, src): def post_comment(owner, repo, number, body): - _api(f"{GITEA_API}/repos/{owner}/{repo}/issues/{number}/comments", GITEA_TOKEN, - method="POST", data={"body": body}) + status, c = _api(f"{GITEA_API}/repos/{owner}/{repo}/issues/{number}/comments", GITEA_TOKEN, + method="POST", data={"body": body}) + return c.get("id") if status in (200, 201) and c else None + + +def edit_comment(owner, repo, comment_id, body): + _api(f"{GITEA_API}/repos/{owner}/{repo}/issues/comments/{comment_id}", GITEA_TOKEN, + method="PATCH", data={"body": body}) + + +def build_status(num): + status, b = _api(f"{DRONE_URL}/api/repos/{CI_REPO}/builds/{num}", DRONE_TOKEN, scheme="Bearer") + return b.get("status") if status == 200 and b else None + + +_TERMINAL = {"success", "failure", "error", "killed"} + + +def watch_and_reflect(owner, name, number, num, recipe, sha, comment_id, run_url): + """Poll the Drone build to completion, then edit the PR comment to reflect the outcome (D7). + Bounded by the build timeout (60m) + margin.""" + import time as _t + deadline = _t.time() + 75 * 60 + last = None + while _t.time() < deadline: + last = build_status(num) + if last in _TERMINAL: + break + _t.sleep(15) + icon = {"success": "✅"}.get(last, "❌") + verdict = "passed" if last == "success" else (last or "did not complete") + if comment_id: + edit_comment(owner, name, comment_id, + f"cc-ci: run for `{recipe}` @ `{sha[:8]}` {icon} **{verdict}** → {run_url}") + log(f"reflected outcome build {num} ({recipe} PR #{number}): {last}") def list_open_prs(full_name): @@ -159,10 +192,14 @@ def process_testme(full_name, owner, name, number, user, comment_id, source): post_comment(owner, name, number, "cc-ci: failed to start a CI run (see bridge logs).") return None, "trigger failed" run_url = f"{DRONE_URL}/{CI_REPO}/{num}" - post_comment(owner, name, number, - f"cc-ci: started CI run for `{name}` @ `{head['sha'][:8]}` → {run_url}") + cid = post_comment(owner, name, number, + f"cc-ci: started CI run for `{name}` @ `{head['sha'][:8]}` → {run_url}") log(f"[{source}] triggered build {num} for {name}@{head['sha'][:8]} " f"(PR #{number}, comment {comment_id}) by {user}") + # Reflect the final pass/fail back onto that comment when the build finishes (D7). + threading.Thread(target=watch_and_reflect, + args=(owner, name, number, num, name, head["sha"], cid, run_url), + daemon=True).start() return run_url, "ok" diff --git a/modules/bridge.nix b/modules/bridge.nix index b5e2d54..e7dbaf9 100644 --- a/modules/bridge.nix +++ b/modules/bridge.nix @@ -10,9 +10,14 @@ let cp ${../bridge/bridge.py} $out/app/bridge.py ''; + # Content-derived tag so `docker stack deploy` rolls the service whenever bridge.py changes + # (a fixed `:latest` + unchanged stack spec does NOT roll — swarm sees no change). + imageTag = builtins.substring 0 12 (builtins.hashString "sha256" + (builtins.readFile ../bridge/bridge.py)); + image = pkgs.dockerTools.buildLayeredImage { name = "cc-ci-bridge"; - tag = "latest"; + tag = imageTag; contents = [ pkgs.python3 pkgs.cacert bridgeApp ]; config = { Cmd = [ "${pkgs.python3}/bin/python3" "/app/bridge.py" ]; @@ -25,7 +30,7 @@ let version: "3.8" services: app: - image: cc-ci-bridge:latest + image: cc-ci-bridge:${imageTag} environment: - GITEA_API=https://git.autonomic.zone/api/v1 - DRONE_URL=https://drone.ci.commoninternet.net