2323from sqlalchemy .sql .functions import count
2424from werkzeug .utils import secure_filename
2525
26+ from database import DeclEnum
2627from decorators import get_menu_entries , template_renderer
2728from mailer import Mailer
2829from mod_auth .controllers import check_access_rights , login_required
@@ -55,6 +56,13 @@ class Status:
5556 FAILURE = "failure"
5657
5758
59+ class Workflow_builds (DeclEnum ):
60+ """Define GitHub Action workflow build names."""
61+
62+ LINUX = "Build CCExtractor on Linux"
63+ WINDOWS = "Build CCExtractor on Windows"
64+
65+
5866@mod_ci .before_app_request
5967def before_app_request () -> None :
6068 """Organize menu content such as Platform management before request."""
@@ -431,9 +439,9 @@ def save_xml_to_file(xml_node, folder_name, file_name) -> None:
431439 )
432440
433441
434- def queue_test (db , gh_commit , commit , test_type , branch = "master" , pr_nr = 0 ) -> None :
442+ def add_test_entry (db , gh_commit , commit , test_type , branch = "master" , pr_nr = 0 ) -> None :
435443 """
436- Store test details into Test model for each platform, and post the status to GitHub .
444+ Add test details entry into Test model for each platform.
437445
438446 :param db: Database connection.
439447 :type db: sqlalchemy.orm.scoped_session
@@ -464,6 +472,117 @@ def queue_test(db, gh_commit, commit, test_type, branch="master", pr_nr=0) -> No
464472 windows_test = Test (TestPlatform .windows , test_type , fork .id , branch , commit , pr_nr )
465473 db .add (windows_test )
466474 db .commit ()
475+
476+
477+ def schedule_test (gh_commit , commit , test_type , branch = "master" , pr_nr = 0 ) -> None :
478+ """
479+ Post status to GitHub as waiting for Github Actions completion.
480+
481+ :param gh_commit: The GitHub API call for the commit. Can be None
482+ :type gh_commit: Any
483+ :param commit: The commit hash.
484+ :type commit: str
485+ :param test_type: The type of test
486+ :type test_type: TestType
487+ :param branch: Branch name
488+ :type branch: str
489+ :param pr_nr: Pull Request number, if applicable.
490+ :type pr_nr: int
491+ :return: Nothing
492+ :rtype: None
493+ """
494+ from run import log
495+
496+ if test_type == TestType .pull_request :
497+ log .debug ('pull request test type detected' )
498+ branch = "pull_request"
499+
500+ if gh_commit is not None :
501+ for platform in TestPlatform :
502+ try :
503+ gh_commit .post (
504+ state = Status .PENDING ,
505+ description = "Waiting for actions to complete" ,
506+ context = f"CI - { platform .value } " ,
507+ )
508+ except ApiError as a :
509+ log .critical (f'Could not post to GitHub! Response: { a .response } ' )
510+
511+
512+ def deschedule_test (gh_commit , commit , test_type , message = "Tests have been cancelled" , branch = "master" , pr_nr = 0 ,
513+ state = Status .FAILURE ) -> None :
514+ """
515+ Post status to GitHub (default: as failure due to Github Actions incompletion).
516+
517+ :param gh_commit: The GitHub API call for the commit. Can be None
518+ :type gh_commit: Any
519+ :param commit: The commit hash.
520+ :type commit: str
521+ :param test_type: The type of test
522+ :type test_type: TestType
523+ :param branch: Branch name
524+ :type branch: str
525+ :param pr_nr: Pull Request number, if applicable.
526+ :type pr_nr: int
527+ :return: Nothing
528+ :rtype: None
529+ """
530+ from run import log
531+
532+ if gh_commit is not None :
533+ for platform in TestPlatform :
534+ try :
535+ gh_commit .post (
536+ state = state ,
537+ description = message ,
538+ context = f"CI - { platform .value } " ,
539+ )
540+ except ApiError as a :
541+ log .critical (f'Could not post to GitHub! Response: { a .response } ' )
542+
543+
544+ def queue_test (db , gh_commit , commit , test_type , branch = "master" , pr_nr = 0 ) -> None :
545+ """
546+ Store test details into Test model for each platform, and post the status to GitHub.
547+
548+ :param db: Database connection.
549+ :type db: sqlalchemy.orm.scoped_session
550+ :param gh_commit: The GitHub API call for the commit. Can be None
551+ :type gh_commit: Any
552+ :param commit: The commit hash.
553+ :type commit: str
554+ :param test_type: The type of test
555+ :type test_type: TestType
556+ :param branch: Branch name
557+ :type branch: str
558+ :param pr_nr: Pull Request number, if applicable.
559+ :type pr_nr: int
560+ :return: Nothing
561+ :rtype: None
562+ """
563+ from run import log
564+
565+ fork_url = f"%/{ g .github ['repository_owner' ]} /{ g .github ['repository' ]} .git"
566+ fork = Fork .query .filter (Fork .github .like (fork_url )).first ()
567+
568+ if test_type == TestType .pull_request :
569+ log .debug ('pull request test type detected' )
570+ branch = "pull_request"
571+
572+ linux_test = Test .query .filter (and_ (Test .platform == TestPlatform .linux ,
573+ Test .commit == commit ,
574+ Test .fork_id == fork .id ,
575+ Test .test_type == test_type ,
576+ Test .branch == branch ,
577+ Test .pr_nr == pr_nr
578+ )).first ()
579+ windows_test = Test .query .filter (and_ (Test .platform == TestPlatform .windows ,
580+ Test .commit == commit ,
581+ Test .fork_id == fork .id ,
582+ Test .test_type == test_type ,
583+ Test .branch == branch ,
584+ Test .pr_nr == pr_nr
585+ )).first ()
467586 add_customized_regression_tests (linux_test .id )
468587 add_customized_regression_tests (windows_test .id )
469588
@@ -595,7 +714,7 @@ def start_ci():
595714
596715 last_commit .value = ref ['object' ]['sha' ]
597716 g .db .commit ()
598- queue_test (g .db , github_status , commit_hash , TestType .commit )
717+ add_test_entry (g .db , github_status , commit_hash , TestType .commit )
599718 else :
600719 g .log .warning ('Unknown push type! Dumping payload for analysis' )
601720 g .log .warning (payload )
@@ -617,14 +736,8 @@ def start_ci():
617736 user_id = payload ['pull_request' ]['user' ]['id' ]
618737 if BlockedUsers .query .filter (BlockedUsers .user_id == user_id ).first () is not None :
619738 g .log .warning ("User Blacklisted" )
620- github_status .post (
621- state = Status .ERROR ,
622- description = "CI start aborted. You may be blocked from accessing this functionality" ,
623- target_url = url_for ('home.index' , _external = True )
624- )
625739 return 'ERROR'
626-
627- queue_test (g .db , github_status , commit_hash , TestType .pull_request , pr_nr = pr_nr )
740+ add_test_entry (g .db , github_status , commit_hash , TestType .pull_request , pr_nr = pr_nr )
628741
629742 elif payload ['action' ] == 'closed' :
630743 g .log .debug ('PR was closed, no after hash available' )
@@ -636,12 +749,15 @@ def start_ci():
636749 continue
637750 progress = TestProgress (test .id , TestStatus .canceled , "PR closed" , datetime .datetime .now ())
638751 g .db .add (progress )
639- repository .statuses (test .commit ).post (
640- state = Status .FAILURE ,
641- description = "Tests canceled" ,
642- context = f"CI - { test .platform .value } " ,
643- target_url = url_for ('test.by_id' , test_id = test .id , _external = True )
644- )
752+ # If test run status exists, mark them as cancelled
753+ for status in repository .commits (test .commit ).status .get ()["statuses" ]:
754+ if status ["context" ] == f"CI - { test .platform .value } " :
755+ repository .statuses (test .commit ).post (
756+ state = Status .FAILURE ,
757+ description = "Tests cancelled" ,
758+ context = f"CI - { test .platform .value } " ,
759+ target_url = url_for ('test.by_id' , test_id = test .id , _external = True )
760+ )
645761
646762 elif event == "issues" :
647763 g .log .debug ('issues event detected' )
@@ -707,6 +823,68 @@ def start_ci():
707823 else :
708824 g .log .warning (f"Unsupported release action: { action } " )
709825
826+ elif event == "workflow_run" :
827+ workflow_name = payload ['workflow_run' ]['name' ]
828+ if workflow_name in [Workflow_builds .LINUX , Workflow_builds .WINDOWS ]:
829+ g .log .debug ('workflow_run event detected' )
830+ commit_hash = payload ['workflow_run' ]['head_sha' ]
831+ github_status = repository .statuses (commit_hash )
832+
833+ if payload ['action' ] == "completed" :
834+ is_complete = True
835+ has_failed = False
836+ builds = {"linux" : False , "windows" : False }
837+ for workflow in repository .actions .runs .get (
838+ event = payload ['workflow_run' ]['event' ],
839+ actor = payload ['sender' ]['login' ],
840+ branch = payload ['workflow_run' ]['head_branch' ]
841+ )['workflow_runs' ]:
842+ if workflow ['head_sha' ] == commit_hash :
843+ if workflow ['status' ] == "completed" :
844+ if workflow ['conclusion' ] != "success" :
845+ has_failed = True
846+ break
847+ if workflow ['name' ] == "Build CCExtractor on Linux" :
848+ builds ["linux" ] = True
849+ elif workflow ['name' ] == "Build CCExtractor on Windows" :
850+ builds ["windows" ] = True
851+ elif workflow ['status' ] != "completed" :
852+ is_complete = False
853+ break
854+
855+ if has_failed :
856+ # no runs to be scheduled since build failed
857+ deschedule_test (github_status , commit_hash , TestType .commit ,
858+ message = "Cancelling tests as Github Action(s) failed" )
859+ elif is_complete :
860+ if payload ['workflow_run' ]['event' ] == "pull_request" :
861+ # In case of pull request run tests only if it is still in an open state
862+ # and user is not blacklisted
863+ for pull_request in repository .pulls .get (state = "open" ):
864+ if pull_request ['head' ]['sha' ] == commit_hash and any (builds .values ()):
865+ user_id = pull_request ['user' ]['id' ]
866+ if BlockedUsers .query .filter (BlockedUsers .user_id == user_id ).first () is not None :
867+ g .log .warning ("User Blacklisted" )
868+ github_status .post (
869+ state = Status .ERROR ,
870+ description = "CI start aborted. \
871+ You may be blocked from accessing this functionality" ,
872+ target_url = url_for ('home.index' , _external = True )
873+ )
874+ return 'ERROR'
875+ queue_test (g .db , github_status , commit_hash ,
876+ TestType .pull_request , pr_nr = pull_request ['number' ])
877+ elif any (builds .values ()):
878+ queue_test (g .db , github_status , commit_hash , TestType .commit )
879+ else :
880+ deschedule_test (github_status , commit_hash , TestType .commit ,
881+ message = "Not ran - no code changes" , state = Status .SUCCESS )
882+ elif payload ['action' ] == 'requested' :
883+ schedule_test (github_status , commit_hash , TestType .commit )
884+ else :
885+ g .log .warning ('Unknown action type in workflow_run! Dumping payload for analysis' )
886+ g .log .warning (payload )
887+
710888 else :
711889 g .log .warning (f'CI unrecognized event: { event } ' )
712890
0 commit comments