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.
377405def 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):
815833def 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
824841def 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
0 commit comments