From 2656885d873306b76166a0bc653201f3ae23045d Mon Sep 17 00:00:00 2001 From: "Sondre B. Risanger" <168830227+sondrebr@users.noreply.github.com> Date: Mon, 24 Aug 2026 15:13:25 +0200 Subject: [PATCH 1/6] Add `pr_title` property to EventInfo w/ tests --- tests/test_tools_event_info.py | 6 +++++- tools/event_info.py | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/test_tools_event_info.py b/tests/test_tools_event_info.py index 68e35a56..c419846e 100644 --- a/tests/test_tools_event_info.py +++ b/tests/test_tools_event_info.py @@ -27,7 +27,8 @@ "action", "comment_id", "comment_body", "comment_created_by", "event_id", "event_triggered_by", "event_type", "issue_number", "issue_url", "label_name", - "pr_number", "pr_merged_status", "pr_url", "repo_name", + "pr_number", "pr_title", "pr_merged_status", "pr_url", + "repo_name", ] # Event type + action combinations with sample event files @@ -177,6 +178,7 @@ def test_GitHubEventInfo(_): # Test properties for pull_request events assert event_info_obj.pr_number == event_info_dict["raw_request_body"]["pull_request"]["number"] + assert event_info_obj.pr_title == event_info_dict["raw_request_body"]["pull_request"]["title"] assert event_info_obj.pr_url == event_info_dict["raw_request_body"]["pull_request"]["html_url"] # Test properties for pull_request opened @@ -248,6 +250,7 @@ def test_GitLabEventInfo(_): # Test properties for pull_request events assert event_info_obj.pr_number == event_info_dict["raw_request_body"]["object_attributes"]["iid"] + assert event_info_obj.pr_title == event_info_dict["raw_request_body"]["object_attributes"]["title"] assert event_info_obj.pr_url == event_info_dict["raw_request_body"]["object_attributes"]["url"] # Test properties for pull_request opened @@ -310,6 +313,7 @@ def test_GitLabEventInfo(_): assert event_info_obj.issue_number == event_info_dict["raw_request_body"]["merge_request"]["iid"] assert event_info_obj.issue_url == event_info_dict["raw_request_body"]["merge_request"]["url"] assert event_info_obj.pr_number == event_info_dict["raw_request_body"]["merge_request"]["iid"] + assert event_info_obj.pr_title == event_info_dict["raw_request_body"]["merge_request"]["title"] pr_merged_status = (event_info_dict["raw_request_body"]["merge_request"]["state"] == "merged") assert event_info_obj.pr_merged_status is pr_merged_status assert event_info_obj.pr_url == event_info_dict["raw_request_body"]["merge_request"]["url"] diff --git a/tools/event_info.py b/tools/event_info.py index 065a7174..0bd01f19 100644 --- a/tools/event_info.py +++ b/tools/event_info.py @@ -89,6 +89,10 @@ def label_name(self): def pr_number(self): raise NotImplementedError() + @cached_property + def pr_title(self): + raise NotImplementedError() + @cached_property def pr_merged_status(self): raise NotImplementedError() @@ -154,6 +158,10 @@ def label_name(self): def pr_number(self): return self._request_body["pull_request"]["number"] + @cached_property + def pr_title(self): + return self._request_body["pull_request"]["title"] + @cached_property def pr_merged_status(self): return self._request_body["pull_request"]["merged"] @@ -304,6 +312,14 @@ def pr_number(self): pr_iid = self._request_body["merge_request"]["iid"] return pr_iid + @cached_property + def pr_title(self): + if self.event_type == "pull_request": + pr_title = self._object_attributes["title"] + else: + pr_title = self._request_body["merge_request"]["title"] + return pr_title + @cached_property def pr_merged_status(self): if self.event_type == "pull_request": From a84b947252925cb4d159e952805e746740b7448e Mon Sep 17 00:00:00 2001 From: "Sondre B. Risanger" <168830227+sondrebr@users.noreply.github.com> Date: Wed, 26 Aug 2026 12:57:03 +0200 Subject: [PATCH 2/6] Add fallback to `GitHubEventInfo.pr_number` Fall back to `GitHubEventInfo.issue_number` for `issue_comment` events --- tests/test_tools_event_info.py | 2 ++ tools/event_info.py | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/test_tools_event_info.py b/tests/test_tools_event_info.py index c419846e..93dbf14c 100644 --- a/tests/test_tools_event_info.py +++ b/tests/test_tools_event_info.py @@ -211,6 +211,8 @@ def test_GitHubEventInfo(_): assert event_info_obj.comment_body == event_info_dict["raw_request_body"]["comment"]["body"] assert event_info_obj.issue_number == event_info_dict["raw_request_body"]["issue"]["number"] assert event_info_obj.issue_url == event_info_dict["raw_request_body"]["issue"]["html_url"] + # 'pr_number' should fall back to 'issue_number' for issue_comment events + assert event_info_obj.pr_number == event_info_obj.issue_number # Test issue_comment created assert event_info_obj.action == "created" diff --git a/tools/event_info.py b/tools/event_info.py index 0bd01f19..65eed87c 100644 --- a/tools/event_info.py +++ b/tools/event_info.py @@ -156,7 +156,12 @@ def label_name(self): @cached_property def pr_number(self): - return self._request_body["pull_request"]["number"] + if self.event_type == "pull_request": + pr_number = self._request_body["pull_request"]["number"] + else: + # Allow accessing 'pr_number' when handling 'issue_comment' events by falling back to 'issue_number' + pr_number = self.issue_number + return pr_number @cached_property def pr_title(self): From 5859722fadac4f19b37ddd583b83aa7e1cf03123 Mon Sep 17 00:00:00 2001 From: "Sondre B. Risanger" <168830227+sondrebr@users.noreply.github.com> Date: Wed, 26 Aug 2026 13:25:18 +0200 Subject: [PATCH 3/6] Remove `pr` parameter from `deploy_built_artefacts` Use `event_info` instead --- eessi_bot_event_handler.py | 2 +- tasks/deploy.py | 28 +++++++++++++--------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/eessi_bot_event_handler.py b/eessi_bot_event_handler.py index c747474a..7ab5dcf4 100644 --- a/eessi_bot_event_handler.py +++ b/eessi_bot_event_handler.py @@ -421,7 +421,7 @@ def handle_pull_request_labeled_event(self, event_info, pr): create_comment(repo_name, pr_number, comment_body, ChatLevels.BASIC) elif label == "bot:deploy": # run function to deploy built artefacts - deploy_built_artefacts(pr, event_info) + deploy_built_artefacts(event_info) else: self.log("handle_pull_request_labeled_event: no handler for label '%s'", label) diff --git a/tasks/deploy.py b/tasks/deploy.py index a0b12bad..585f5fb0 100644 --- a/tasks/deploy.py +++ b/tasks/deploy.py @@ -620,41 +620,41 @@ def determine_artefacts_to_deploy(successes, upload_policy): return to_be_deployed -def deploy_built_artefacts(pr, event_info): +def deploy_built_artefacts(event_info): """ Deploy built artefacts. Args: - pr (github.PullRequest.PullRequest): PyGithub instance for the pull request - event_info (dict): dictionary containing event information + event_info (EventInfo): dictionary containing event information Returns: None (implicitly) """ funcname = sys._getframe().f_code.co_name - log(f"{funcname}(): deploy for PR {pr.number}") + repo_name = event_info.repo_name + pr_number = event_info.pr_number + labeler = event_info.event_triggered_by + + log(f"{funcname}(): deploy for PR {pr_number}") cfg = config.read_config() deploy_cfg = cfg[config.SECTION_DEPLOYCFG] deploy_permission = deploy_cfg.get(config.DEPLOYCFG_SETTING_DEPLOY_PERMISSION, '') log(f"{funcname}(): deploy permission '{deploy_permission}'") - labeler = event_info['raw_request_body']['sender']['login'] - - # verify that the GitHub account that set label bot:deploy has the + # verify that the account that set label bot:deploy has the # permission to trigger the deployment if labeler not in deploy_permission.split(): - log(f"{funcname}(): GH account '{labeler}' is not authorized to deploy") + log(f"{funcname}(): account '{labeler}' is not authorized to deploy") no_deploy_permission_comment = deploy_cfg.get(config.DEPLOYCFG_SETTING_NO_DEPLOY_PERMISSION_COMMENT) - repo_name = event_info["raw_request_body"]["repository"]["full_name"] pr_comments.create_comment(repo_name, - pr.number, + pr_number, no_deploy_permission_comment.format(deploy_labeler=labeler), ChatLevels.CHATTY) return else: - log(f"{funcname}(): GH account '{labeler}' is authorized to deploy") + log(f"{funcname}(): account '{labeler}' is authorized to deploy") # get upload policy from config upload_policy = deploy_cfg.get(config.DEPLOYCFG_SETTING_UPLOAD_POLICY) @@ -669,7 +669,7 @@ def deploy_built_artefacts(pr, event_info): # 4) call function to deploy a single artefact per software subdir # 1) determine the jobs that have been run for the PR - job_dirs = determine_job_dirs(pr.number) + job_dirs = determine_job_dirs(pr_number) log(f"{funcname}(): job_dirs = {','.join(job_dirs)}") # 2) for each job, check its status (SUCCESS or FAILURE) @@ -680,10 +680,8 @@ def deploy_built_artefacts(pr, event_info): to_be_deployed = determine_artefacts_to_deploy(successes, upload_policy) # 4) call function to deploy a single artefact per software subdir - repo_name = pr.base.repo.full_name - for job in to_be_deployed.values(): job_dir = job['job_dir'] pr_comment_id = job['pr_comment_id'] artefact = job['artefact'] - upload_artefact(job_dir, artefact, repo_name, pr.number, pr_comment_id) + upload_artefact(job_dir, artefact, repo_name, pr_number, pr_comment_id) From 74bb5726f4014d9100cbdcb01896cbe490e49d1a Mon Sep 17 00:00:00 2001 From: "Sondre B. Risanger" <168830227+sondrebr@users.noreply.github.com> Date: Wed, 26 Aug 2026 14:53:17 +0200 Subject: [PATCH 4/6] Remove `pr` param from `handle_pull_request_*_event` Use `event_info` instead --- eessi_bot_event_handler.py | 82 ++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 47 deletions(-) diff --git a/eessi_bot_event_handler.py b/eessi_bot_event_handler.py index 7ab5dcf4..aa6fefa0 100644 --- a/eessi_bot_event_handler.py +++ b/eessi_bot_event_handler.py @@ -387,30 +387,28 @@ def handle_installation_event(self, event_info, log_file=None): self.log("App installation event by user %s with action '%s'", user, action) self.log("installation event handled!") - def handle_pull_request_labeled_event(self, event_info, pr): + def handle_pull_request_labeled_event(self, event_info): """ Handle events of type pull_request with the action labeled. Main action is to process the label 'bot:deploy'. Args: - event_info (dict): event received by event_handler - pr (github.PullRequest.PullRequest): instance representing the pull request + event_info (EventInfo): event received by event_handler Returns: None (implicitly) """ # determine label - label = event_info['raw_request_body']['label']['name'] - self.log("Process PR labeled event: PR#%s, label '%s'", pr.number, label) + repo_name = event_info.repo_name + pr_number = event_info.pr_number + label = event_info.label_name + self.log("Process PR labeled event: PR#%s, label '%s'", pr_number, label) if label == "bot:build": msg = "Handling the label 'bot:build' is disabled. Use the command `bot: build [FILTER]*` instead." self.log(msg) - request_body = event_info['raw_request_body'] - repo_name = request_body['repository']['full_name'] - pr_number = request_body['pull_request']['number'] app_name = self.cfg[config.SECTION_GITHUB][config.GITHUB_SETTING_APP_NAME] command_response_fmt = self.cfg[config.SECTION_BOT_CONTROL][config.BOT_CONTROL_SETTING_COMMAND_RESPONSE_FMT] comment_body = command_response_fmt.format( @@ -425,19 +423,17 @@ def handle_pull_request_labeled_event(self, event_info, pr): else: self.log("handle_pull_request_labeled_event: no handler for label '%s'", label) - def handle_pull_request_opened_event(self, event_info, pr, req_chatlevel=ChatLevels.CHATTY): + def handle_pull_request_opened_event(self, event_info, req_chatlevel=ChatLevels.CHATTY): """ Handle events of type pull_request with the action opened. Main action is to report for which architectures and repositories a bot instance is configured to build for. Args: - event_info (dict): event received by event_handler - pr (github.PullRequest.PullRequest): instance representing the pull request + event_info (EventInfo): event received by event_handler Returns: - github.IssueComment.IssueComment instance or None (note, github refers to - PyGithub, not the github from the internal connections module) + PRComment instance or None """ self.log("PR opened: waiting for label bot:build") app_name = self.cfg[config.SECTION_GITHUB][config.GITHUB_SETTING_APP_NAME] @@ -462,8 +458,9 @@ def handle_pull_request_opened_event(self, event_info, pr, req_chatlevel=ChatLev self.log(f"PR opened: comment '{comment}'") # create comment to pull request - repo_name = pr.base.repo.full_name - issue_comment = create_comment(repo_name, pr.number, comment, req_chatlevel) + repo_name = event_info.repo_name + pr_number = event_info.pr_number + issue_comment = create_comment(repo_name, pr_number, comment, req_chatlevel) return issue_comment def handle_pull_request_event(self, event_info, log_file=None): @@ -472,24 +469,23 @@ def handle_pull_request_event(self, event_info, log_file=None): determining a handler for it. Args: - event_info (dict): event received by event_handler + event_info (EventInfo): event received by event_handler log_file (string): path to log messages to Returns: None (implicitly) """ - action = event_info['action'] - gh = github.get_instance() - self.log("repository: '%s'", event_info['raw_request_body']['repository']['full_name']) - pr = gh.get_repo(event_info['raw_request_body']['repository'] - ['full_name']).get_pull(event_info['raw_request_body']['pull_request']['number']) - self.log("PR data: %s", pr) + action = event_info.action + pr_number = event_info.pr_number + self.log(f"Repository: '{event_info.repo_name}'") + self.log(f"PR title: '{event_info.pr_title}'") + self.log(f"PR number: {pr_number}") handler_name = 'handle_pull_request_%s_event' % action if hasattr(self, handler_name): handler = getattr(self, handler_name) - self.log("Handling PR action '%s' for PR #%d...", action, pr.number) - handler(event_info, pr) + self.log("Handling PR action '%s' for PR #%d...", action, pr_number) + handler(event_info) else: self.log("No handler for PR action '%s'", action) @@ -600,7 +596,7 @@ def handle_bot_command_show_config(self, event_info, bot_command): type pull_request with the action opened. Args: - event_info (dict): event received by event_handler + event_info (EventInfo): event received by event_handler bot_command (EESSIBotCommand): command to be handled Returns: @@ -608,11 +604,7 @@ def handle_bot_command_show_config(self, event_info, bot_command): by the handler for events of type pull_request with the action opened """ self.log("processing bot command 'show_config'") - gh = github.get_instance() - repo_name = event_info['raw_request_body']['repository']['full_name'] - pr_number = event_info['raw_request_body']['issue']['number'] - pr = gh.get_repo(repo_name).get_pull(pr_number) - issue_comment = self.handle_pull_request_opened_event(event_info, pr, req_chatlevel=ChatLevels.MINIMAL) + issue_comment = self.handle_pull_request_opened_event(event_info, req_chatlevel=ChatLevels.MINIMAL) if issue_comment: return f"\n - added comment {issue_comment.html_url} to show configuration" @@ -801,40 +793,38 @@ def start(self, app, port=3000): self.log(log_file_info) waitress.serve(app, listen='*:%s' % port) - def handle_pull_request_closed_event(self, event_info, pr): + def handle_pull_request_closed_event(self, event_info): """ Handle events of type pull_request with the action 'closed'. It determines used by the PR and moves them to the trash_bin. It also adds information to the logs and a comment to the PR. Args: - event_info (dict): event received by event_handler - pr (github.PullRequest.PullRequest): instance representing the pull request + event_info (EventInfo): event received by event_handler Returns: - github.IssueComment.IssueComment instance or None (note, github refers to - PyGithub, not the github from the internal connections module) + PRComment instance or None """ + repo_name = event_info.repo_name + pr_number = event_info.pr_number # Detect event and report if PR was merged or closed - request_body = event_info['raw_request_body'] # next value: True -> PR merged, False -> PR closed - mergedOrClosed = request_body['pull_request']['merged'] + mergedOrClosed = event_info.pr_merged_status status = "merged" if mergedOrClosed else "closed" - self.log(f"PR {pr.number}: PR got {status} (json value: {mergedOrClosed})") + self.log(f"PR {pr_number}: PR got {status} (json value: {mergedOrClosed})") # 1) determine the jobs that have been run for the PR - self.log(f"PR {pr.number}: determining directories to be moved to trash bin") - job_dirs = determine_job_dirs(pr.number) + self.log(f"PR {pr_number}: determining directories to be moved to trash bin") + job_dirs = determine_job_dirs(pr_number) if job_dirs == []: - self.log(f"PR {pr.number}: No job directories found; nothing to move.") + self.log(f"PR {pr_number}: No job directories found; nothing to move.") else: # 2) Get trash_bin_dir from configs trash_bin_root_dir = self.cfg[config.SECTION_CLEAN_UP][config.CLEAN_UP_SETTING_TRASH_BIN_ROOT_DIR] - repo_name = request_body['repository']['full_name'] dt_start = datetime.now(timezone.utc) trash_bin_dir = "/".join([trash_bin_root_dir, repo_name, dt_start.strftime('%Y.%m.%d')]) @@ -842,19 +832,17 @@ def handle_pull_request_closed_event(self, event_info, pr): # cron job deletes symlinks? # 3) move the directories to the trash_bin - self.log(f"PR {pr.number}: moving directories to trash bin {trash_bin_dir}") + self.log(f"PR {pr_number}: moving directories to trash bin {trash_bin_dir}") move_to_trash_bin(trash_bin_dir, job_dirs) dt_end = datetime.now(timezone.utc) dt_delta = dt_end - dt_start seconds_elapsed = dt_delta.days * 24 * 3600 + dt_delta.seconds - self.log(f"PR {pr.number}: moved directories to trash bin {trash_bin_dir} (took {seconds_elapsed} seconds)") + self.log(f"PR {pr_number}: moved directories to trash bin {trash_bin_dir} (took {seconds_elapsed} seconds)") # 4) report move to pull request - - repo_name = pr.base.repo.full_name clean_up_comment = self.cfg[config.SECTION_CLEAN_UP][config.CLEAN_UP_SETTING_MOVED_JOB_DIRS_COMMENT] moved_comment = clean_up_comment.format(job_dirs=job_dirs, trash_bin_dir=trash_bin_dir) - issue_comment = create_comment(repo_name, pr.number, moved_comment, ChatLevels.CHATTY) + issue_comment = create_comment(repo_name, pr_number, moved_comment, ChatLevels.CHATTY) return issue_comment From 4c6ed7852d02ae2e665d16df0984eebabc5b4229 Mon Sep 17 00:00:00 2001 From: "Sondre B. Risanger" <168830227+sondrebr@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:27:53 +0200 Subject: [PATCH 5/6] Add GitLab support for pull_request opened events pull_request labeled and closed events are also "working" for GL but not actually supported: For closed events, the bot responds with a '... not yet supported' message; For labeled events, the bot responds with a '... not yet supported' message if the label is 'bot:deploy'. --- eessi_bot_event_handler.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/eessi_bot_event_handler.py b/eessi_bot_event_handler.py index aa6fefa0..2653e61e 100644 --- a/eessi_bot_event_handler.py +++ b/eessi_bot_event_handler.py @@ -38,7 +38,7 @@ from tools.commands import EESSIBotCommand, EESSIBotCommandError, \ contains_any_bot_command, get_bot_command, get_supported_commands, ALL_COMMANDS from tools.event_info import create_event_info_instance -from tools.git import connect_to_git_hosting_platform, get_app_name, get_git_hosting_platform +from tools.git import connect_to_git_hosting_platform, get_app_name, get_git_hosting_platform, GITLAB from tools.permissions import check_command_permission from tools.pr_comments import ChatLevels, create_comment @@ -409,7 +409,7 @@ def handle_pull_request_labeled_event(self, event_info): msg = "Handling the label 'bot:build' is disabled. Use the command `bot: build [FILTER]*` instead." self.log(msg) - app_name = self.cfg[config.SECTION_GITHUB][config.GITHUB_SETTING_APP_NAME] + app_name = get_app_name(self.cfg) command_response_fmt = self.cfg[config.SECTION_BOT_CONTROL][config.BOT_CONTROL_SETTING_COMMAND_RESPONSE_FMT] comment_body = command_response_fmt.format( app_name=app_name, @@ -418,6 +418,12 @@ def handle_pull_request_labeled_event(self, event_info): ) create_comment(repo_name, pr_number, comment_body, ChatLevels.BASIC) elif label == "bot:deploy": + if get_git_hosting_platform(self.cfg) == GITLAB: + GL_PR_LABELED_NOT_SUPPORTED = "The `bot:deploy` label was added to this MR. " \ + "Deployment is not yet supported on GitLab." + create_comment(repo_name, pr_number, GL_PR_LABELED_NOT_SUPPORTED, ChatLevels.BASIC) + return + # run function to deploy built artefacts deploy_built_artefacts(event_info) else: @@ -436,7 +442,7 @@ def handle_pull_request_opened_event(self, event_info, req_chatlevel=ChatLevels. PRComment instance or None """ self.log("PR opened: waiting for label bot:build") - app_name = self.cfg[config.SECTION_GITHUB][config.GITHUB_SETTING_APP_NAME] + app_name = get_app_name(self.cfg) # TODO check if PR already has a comment with arch targets and # repositories node_map = get_node_types(self.cfg) @@ -489,6 +495,9 @@ def handle_pull_request_event(self, event_info, log_file=None): else: self.log("No handler for PR action '%s'", action) + # PyGHee gets the event type by subscripting event_info, i.e., it gets 'merge_request' for GL PR events + handle_merge_request_event = handle_pull_request_event + def handle_bot_command(self, event_info, bot_command, log_file=None): """ Handle a bot command. Main purpose is to determine a handler for the @@ -808,6 +817,11 @@ def handle_pull_request_closed_event(self, event_info): repo_name = event_info.repo_name pr_number = event_info.pr_number + if get_git_hosting_platform(self.cfg) == GITLAB: + GL_PR_CLOSED_NOT_SUPPORTED = "The MR was closed. Job directory cleanup is not yet supported on GitLab." + create_comment(repo_name, pr_number, GL_PR_CLOSED_NOT_SUPPORTED, ChatLevels.CHATTY) + return + # Detect event and report if PR was merged or closed # next value: True -> PR merged, False -> PR closed mergedOrClosed = event_info.pr_merged_status From 08a28226dee115a4ede9dd27b0bfd605d1f79a87 Mon Sep 17 00:00:00 2001 From: "Sondre B. Risanger" <168830227+sondrebr@users.noreply.github.com> Date: Thu, 27 Aug 2026 09:53:56 +0200 Subject: [PATCH 6/6] Add GitLab support for `bot: show_config` --- README.md | 2 +- tools/commands.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3a837fd6..b6cbb096 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ The bot consists of two main components provided in this repository: > [!WARNING] > **Limited feature support in GitLab** > -> Note that GitLab support is currently limited to the `help` command. +> Note that GitLab support is currently limited to the `help` and `show_config` commands. ## Prerequisites diff --git a/tools/commands.py b/tools/commands.py index 7fd9a4da..45f6a8a2 100644 --- a/tools/commands.py +++ b/tools/commands.py @@ -25,7 +25,7 @@ ALL_COMMANDS = ["help", "build", "show_config", "status", "cancel"] SUPPORTED_COMMANDS_PER_GIT_HOST = { GITHUB: ["help", "build", "show_config", "status", "cancel"], - GITLAB: ["help"], + GITLAB: ["help", "show_config"], }