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