Skip to content

Commit 20f7f96

Browse files
committed
Fix problem with uniqueness of task and build IDs.
Unlike Cirrus, Github Actions' run and job IDs are only unique per repository. Since cfbot has to deal with updates from postgres/postgres and postgresql-cfbot/postgresql, we need to make them unique. The immediate solution is to prefix the full repo name. (Another option would be to add the "node ID", which is the repo ID, but since the repo string is also useful, let's use that. It would be another matter if we had to tolerate repos being renamed, but we don't.) Now we have: * build_id = repo:run_id.run_attempt * task_id = repo:job_id That should be OK for the CF app, which treats them as opaque strings. In future we might also consider introducing our own surrogate keys for both of those, or we could just keep doing this.
1 parent fae7528 commit 20f7f96

2 files changed

Lines changed: 56 additions & 41 deletions

File tree

cfbot_github.py

Lines changed: 56 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
11
#!/usr/bin/env python3
22
#
3-
# Cfbot uses terminology inherited from the defunct Cirrus CI API, but
4-
# there is a one-to-one mapping of concepts with Github's model:
3+
# Cfbot uses terminology inherited from the defunct Cirrus CI API, and
4+
# we have to do a bit of key swizzling to map the concepts to Github's
5+
# model:
56
#
67
# * build ~= GH [workflow] run (eg run triggered by commit, usually
78
# there is only one per commit unless it is re-run for some reason,
8-
# as was the case with Cirrus). Since GH uses the *same* run ID for
9-
# re-runs, we also append the "attempt" number, separated by ".".
10-
# We don't usually expect more than one run at a time for a given
11-
# commit, and the ID of the most recent build is in branch.build_id.
9+
# as was the case with Cirrus). Unfortunately run_id is not
10+
# globally unique (as Cirrus's was), so build_id is a string of the
11+
# form repo:run_id.run_attempt.
12+
#
13+
# XXX Surrogate key?
1214
#
1315
# * task ~= GH job (eg Windows, Linux, ... being the individual tasks
14-
# of in a build)
16+
# of in a build), but again job_id is not globally unique so task_id
17+
# is a string of the form repo:job_id.
18+
#
19+
# XXX Surrogate key?
1520
#
16-
# * task_command ~= GH job step (eg configure, build, test, ...)
21+
# XXX Note that the cfapp doesn't really need the repo: prefix
22+
# because it only cares about tasks in postgresql-cfbot/postgresql,
23+
# but for now at least it seems simpler to have just one kind of
24+
# task ID across the two systems. Cfbot has to deal with
25+
# postgres/postgres tasks too, and the job_id part might collide.
1726
#
18-
# Even though the IDs of the above are all integers, we process them
19-
# as strings for future-proofing anticipating future alternative
20-
# providers.
27+
# * task_command ~= GH job step (eg configure, build, test, ...).
28+
#
29+
# XXX Not implemented yet...
2130
#
2231
# Cfbot builds and tasks have a single "status" inherited from Cirrus,
2332
# with upper case values. GH has two fields "status" and "conclusion"
@@ -100,6 +109,25 @@ def convert_github_status_and_conclusion(status, conclusion):
100109
return status + ":" + conclusion
101110

102111

112+
def make_build_id(repo, run_id, run_attempt):
113+
return repo + ":" + str(run_id) + "." + str(run_attempt)
114+
115+
116+
def split_build_id(build_id):
117+
repo, rest = build_id.split(":")
118+
run_id, run_attempt = rest.split(".")
119+
return repo, run_id, run_attempt
120+
121+
122+
def make_task_id(repo, job_id):
123+
return repo + ":" + str(job_id)
124+
125+
126+
def split_task_id(task_id):
127+
repo, job_id = task_id.split(":")
128+
return repo, job_id
129+
130+
103131
# Sends a GET request to the Github API and returns the resulting JSON
104132
# as a Python object.
105133
#
@@ -376,10 +404,7 @@ def process_new_build_status(
376404
# relating to this build (run) or a task (job) it contains.
377405
def poll_run(conn, repo, run_id, run_attempt, source):
378406

379-
# What we call a build ID is really (run ID, run attempt). The
380-
# run ID doesn't change when a workflow is re-run, it just gets a
381-
# new attempt number.
382-
build_id = run_id + "." + run_attempt
407+
build_id = make_build_id(repo, run_id, run_attempt)
383408

384409
# What triggered this. This is only used so that the
385410
# (build|task)_status_history table can show when webhooks were
@@ -391,10 +416,10 @@ def poll_run(conn, repo, run_id, run_attempt, source):
391416
# Serialise the API calls about this build by making sure we have a row to
392417
# lock first.
393418
cursor.execute(
394-
"""INSERT INTO build (build_id, repo, created, modified)
395-
VALUES (%s, %s, now(), now())
419+
"""INSERT INTO build (build_id, created, modified)
420+
VALUES (%s, now(), now())
396421
ON CONFLICT DO NOTHING""",
397-
(build_id, repo),
422+
(build_id,),
398423
)
399424
inserted = cursor.rowcount > 0
400425
cursor.execute(
@@ -411,7 +436,6 @@ def poll_run(conn, repo, run_id, run_attempt, source):
411436
repo, "runs/" + run_id + "/attempts/" + run_attempt, none_for_404=True
412437
)
413438

414-
print(json.dumps(build))
415439
if not build:
416440
logging.info(
417441
"Github does not know workflow run %s, existing status %s",
@@ -447,7 +471,7 @@ def poll_run(conn, repo, run_id, run_attempt, source):
447471
# Upsert the tasks.
448472
position = 0
449473
for task in tasks:
450-
task_id = task["id"]
474+
task_id = make_task_id(repo, task["id"])
451475
task_name = task["name"]
452476
task_status = convert_github_status_and_conclusion(
453477
task["status"], task["conclusion"]
@@ -530,9 +554,10 @@ def poll_commit(conn, repo, commit_id):
530554
cursor = conn.cursor()
531555
runs = get_github_api(repo, "runs", params={"head_sha": commit_id})
532556
for run in runs["workflow_runs"]:
533-
build_id = str(run["id"]) + "." + str(run["run_attempt"])
534-
key = repo + ":" + build_id
535-
cfbot_work_queue.insert_work_queue_if_not_exists(cursor, "poll-github-run", key)
557+
build_id = make_build_id(repo, run["id"], run["run_attempt"])
558+
cfbot_work_queue.insert_work_queue_if_not_exists(
559+
cursor, "poll-github-run", build_id
560+
)
536561

537562

538563
# ======================================================================
@@ -600,7 +625,6 @@ def check_stale_builds(conn):
600625
avg_elapsed + stddev_elapsed * 3 as elapsed_p99
601626
from build_status_statistics),
602627
run as (select build_id,
603-
repo,
604628
status,
605629
branch_name,
606630
case
@@ -611,7 +635,6 @@ def check_stale_builds(conn):
611635
from build
612636
where build_status_running(status))
613637
select run.build_id,
614-
run.repo,
615638
run.reference_branch,
616639
run.branch_name,
617640
run.status,
@@ -622,7 +645,6 @@ def check_stale_builds(conn):
622645
where run.elapsed > COALESCE(elapsed_p99, interval '30 minutes')""")
623646
for (
624647
build_id,
625-
build_repo,
626648
reference_branch,
627649
branch_name,
628650
build_status,
@@ -647,7 +669,7 @@ def check_stale_builds(conn):
647669
reference_branch,
648670
)
649671
cfbot_work_queue.insert_work_queue_if_not_exists(
650-
cursor, "poll-github-run", build_repo + ":" + build_id
672+
cursor, "poll-github-run", build_id
651673
)
652674

653675

@@ -663,8 +685,7 @@ def check_stale_tasks(conn):
663685
status,
664686
avg_elapsed + stddev_elapsed * 3 as elapsed_p99
665687
from task_status_statistics),
666-
run as (select build.repo,
667-
task.task_id,
688+
run as (select task.task_id,
668689
task.build_id,
669690
task.task_name,
670691
task.status,
@@ -676,8 +697,7 @@ def check_stale_tasks(conn):
676697
now() - task.modified as elapsed
677698
from task join build using (build_id)
678699
where task_status_running(task.status))
679-
select run.repo,
680-
run.task_id,
700+
select run.task_id,
681701
run.build_id,
682702
run.reference_branch,
683703
run.branch_name,
@@ -689,7 +709,6 @@ def check_stale_tasks(conn):
689709
left join ref on ((run.reference_branch, run.task_name, run.status) = (ref.branch_name, ref.task_name, ref.status))
690710
where run.elapsed > COALESCE(elapsed_p99, interval '30 minutes')""")
691711
for (
692-
build_repo,
693712
task_id,
694713
build_id,
695714
reference_branch,
@@ -718,7 +737,7 @@ def check_stale_tasks(conn):
718737
reference_branch,
719738
)
720739
cfbot_work_queue.insert_work_queue_if_not_exists(
721-
cursor, "poll-github-run", build_repo + ":" + build_id
740+
cursor, "poll-github-run", build_id
722741
)
723742

724743

@@ -791,10 +810,9 @@ def ingest_workflow_job(conn, event):
791810
repo = event["repository"]["full_name"]
792811
run_id = event["workflow_job"]["run_id"]
793812
run_attempt = event["workflow_job"]["run_attempt"]
794-
build_id = str(run_id) + "." + str(run_attempt)
795-
key = repo + ":" + build_id
813+
build_id = make_build_id(repo, run_id, run_attempt)
796814
cfbot_work_queue.insert_work_queue_if_not_exists(
797-
cursor, "poll-github-run-on-webhook", key
815+
cursor, "poll-github-run-on-webhook", build_id
798816
)
799817

800818

@@ -815,17 +833,15 @@ def poll_github_commit(conn, key):
815833
def poll_github_run(conn, key):
816834
# Enqueued by check_stale_builds() when we haven't heard any news
817835
# about a build for a statistically unlikely period of time.
818-
repo, build_id = key.split(":")
819-
run_id, run_attempt = build_id.split(".")
836+
repo, run_id, run_attempt = split_build_id(key)
820837
poll_run(conn, repo, run_id, run_attempt, "poll")
821838

822839

823840
# poll-github-run-on-webhook
824841
def poll_github_run_on_webhook(conn, key):
825842
# Enqueued by ingest_workflow_job() when we receive a webhook
826843
# poke from Github.
827-
repo, build_id = key.split(":")
828-
run_id, run_attempt = build_id.split(".")
844+
repo, run_id, run_attempt = split_build_id(key)
829845
poll_run(conn, repo, run_id, run_attempt, "webhook")
830846

831847

create.sql

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@ ALTER SEQUENCE public.branch_id_seq OWNED BY public.branch.id;
115115

116116
CREATE TABLE public.build (
117117
build_id text NOT NULL,
118-
repo text,
119118
branch_name text,
120119
status text,
121120
commit_id text,

0 commit comments

Comments
 (0)