M8/D7: bridge reflects final pass/fail onto the PR comment + content-hash image tag
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 / ❌ <status> (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) <noreply@anthropic.com>
This commit is contained in:
@ -118,8 +118,41 @@ def trigger_build(recipe, ref, pr, src):
|
|||||||
|
|
||||||
|
|
||||||
def post_comment(owner, repo, number, body):
|
def post_comment(owner, repo, number, body):
|
||||||
_api(f"{GITEA_API}/repos/{owner}/{repo}/issues/{number}/comments", GITEA_TOKEN,
|
status, c = _api(f"{GITEA_API}/repos/{owner}/{repo}/issues/{number}/comments", GITEA_TOKEN,
|
||||||
method="POST", data={"body": body})
|
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):
|
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).")
|
post_comment(owner, name, number, "cc-ci: failed to start a CI run (see bridge logs).")
|
||||||
return None, "trigger failed"
|
return None, "trigger failed"
|
||||||
run_url = f"{DRONE_URL}/{CI_REPO}/{num}"
|
run_url = f"{DRONE_URL}/{CI_REPO}/{num}"
|
||||||
post_comment(owner, name, number,
|
cid = post_comment(owner, name, number,
|
||||||
f"cc-ci: started CI run for `{name}` @ `{head['sha'][:8]}` → {run_url}")
|
f"cc-ci: started CI run for `{name}` @ `{head['sha'][:8]}` → {run_url}")
|
||||||
log(f"[{source}] triggered build {num} for {name}@{head['sha'][:8]} "
|
log(f"[{source}] triggered build {num} for {name}@{head['sha'][:8]} "
|
||||||
f"(PR #{number}, comment {comment_id}) by {user}")
|
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"
|
return run_url, "ok"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -10,9 +10,14 @@ let
|
|||||||
cp ${../bridge/bridge.py} $out/app/bridge.py
|
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 {
|
image = pkgs.dockerTools.buildLayeredImage {
|
||||||
name = "cc-ci-bridge";
|
name = "cc-ci-bridge";
|
||||||
tag = "latest";
|
tag = imageTag;
|
||||||
contents = [ pkgs.python3 pkgs.cacert bridgeApp ];
|
contents = [ pkgs.python3 pkgs.cacert bridgeApp ];
|
||||||
config = {
|
config = {
|
||||||
Cmd = [ "${pkgs.python3}/bin/python3" "/app/bridge.py" ];
|
Cmd = [ "${pkgs.python3}/bin/python3" "/app/bridge.py" ];
|
||||||
@ -25,7 +30,7 @@ let
|
|||||||
version: "3.8"
|
version: "3.8"
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: cc-ci-bridge:latest
|
image: cc-ci-bridge:${imageTag}
|
||||||
environment:
|
environment:
|
||||||
- GITEA_API=https://git.autonomic.zone/api/v1
|
- GITEA_API=https://git.autonomic.zone/api/v1
|
||||||
- DRONE_URL=https://drone.ci.commoninternet.net
|
- DRONE_URL=https://drone.ci.commoninternet.net
|
||||||
|
|||||||
Reference in New Issue
Block a user