Skip to content
52 changes: 33 additions & 19 deletions eessi_bot_event_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@
from tools import config
from tools.args import event_handler_parse
from tools.commands import EESSIBotCommand, EESSIBotCommandError, \
contains_any_bot_command, get_bot_command
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_git_hosting_platform
from tools.git import connect_to_git_hosting_platform, get_app_name, get_git_hosting_platform
from tools.permissions import check_command_permission
from tools.pr_comments import ChatLevels, create_comment

Expand Down Expand Up @@ -188,7 +188,7 @@ def handle_issue_comment_event(self, event_info, log_file=None):
comments for any bot command and execute it if one is found.

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:
Expand All @@ -197,27 +197,26 @@ def handle_issue_comment_event(self, event_info, log_file=None):
Raises:
Exception: raises any exception that is not of type EESSIBotCommandError
"""
request_body = event_info['raw_request_body']
issue_url = request_body['issue']['url']
action = request_body['action']
sender = request_body['sender']['login']
owner = request_body['comment']['user']['login']
repo_name = request_body['repository']['full_name']
pr_number = request_body['issue']['number']
issue_url = event_info.issue_url
action = event_info.action
sender = event_info.event_triggered_by
owner = event_info.comment_created_by
repo_name = event_info.repo_name
pr_number = event_info.issue_number

# TODO add request body text (['comment']['body']) to log message when
# log level is set to debug
self.log(f"Comment in {issue_url} (owned by @{owner}) {action} by @{sender}")

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]

# currently, only commands in new comments are supported
# - commands have the syntax 'bot: COMMAND [ARGS*]'

# only scan for commands in newly created comments
if action == 'created':
comment_received = request_body['comment']['body']
comment_received = event_info.comment_body
self.log(f"comment action '{action}' is handled")
else:
# NOTE we do not respond to an updated PR comment with yet another
Expand Down Expand Up @@ -368,6 +367,9 @@ def handle_issue_comment_event(self, event_info, log_file=None):

self.log(f"issue_comment event (url {issue_url}) handled!")

# PyGHee gets the event type by subscripting event_info, i.e., it gets 'note' for GL comment events
handle_note_event = handle_issue_comment_event

def handle_installation_event(self, event_info, log_file=None):
"""
Handle events of type installation. Main action is to log the event.
Expand Down Expand Up @@ -497,7 +499,7 @@ def handle_bot_command(self, event_info, bot_command, log_file=None):
specific bot_command given.

Args:
event_info (dict): event received by event_handler
event_info (EventInfo): event received by event_handler
bot_command (EESSIBotCommand): command to be handled
log_file (string): path to log messages to

Expand All @@ -512,9 +514,13 @@ def handle_bot_command(self, event_info, bot_command, log_file=None):
cmd = bot_command.command
handler_name = f"handle_bot_command_{cmd}"
if hasattr(self, handler_name):
handler = getattr(self, handler_name)
self.log(f"Handling bot command {cmd}")
return handler(event_info, bot_command)
if cmd in get_supported_commands(self.cfg):
handler = getattr(self, handler_name)
self.log(f"Handling bot command {cmd}")
return handler(event_info, bot_command)
else:
self.log(f"Command '{cmd}' is not supported on the configured Git hosting platform.")
raise EESSIBotCommandError(f"Unsupported command `{cmd}`; use `bot: help` for usage information")
else:
self.log(f"No handler for command '{cmd}'")
raise EESSIBotCommandError(f"unknown command `{cmd}`; use `bot: help` for usage information")
Expand All @@ -525,17 +531,25 @@ def handle_bot_command_help(self, event_info, bot_command):
commands.

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:
(string): basic information about sending commands to the bot
"""
# Create comma-separated lists of supported and unsupported commands
supported_commands = get_supported_commands(self.cfg)
unsupported_commands = [cmd for cmd in ALL_COMMANDS if cmd not in supported_commands]
supported_commands_str = ", ".join([f"`{cmd}`" for cmd in supported_commands])
unsupported_commands_str = ", ".join([f"`{cmd}`" for cmd in unsupported_commands])

help_msg = "\n **How to send commands to bot instances**"
help_msg += "\n - Commands must be sent with a **new** comment (edits of existing comments are ignored)."
help_msg += "\n - A comment may contain multiple commands, one per line."
help_msg += "\n - Every command begins at the start of a line and has the syntax `bot: COMMAND [ARGUMENTS]*`"
help_msg += "\n - Currently supported COMMANDs are: `help`, `build`, `show_config`, `status`, `cancel`"
help_msg += "\n - Currently supported COMMANDs are: " + supported_commands_str
if unsupported_commands_str:
help_msg += "\n - The following COMMANDs are not yet supported: " + unsupported_commands_str
help_msg += "\n"
help_msg += "\n For more information, see https://www.eessi.io/docs/bot"
return help_msg
Expand Down Expand Up @@ -572,7 +586,7 @@ def handle_bot_command_build(self, event_info, bot_command):
else:
for job_id, issue_comment in submitted_jobs.items():
build_msg += f"\n - submitted job `{job_id}`"
if issue_comment:
if issue_comment and issue_comment.html_url:
build_msg += f", for details & status see {issue_comment.html_url}"
else:
request_body = event_info['raw_request_body']
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ python-gitlab==6.5.0;python_version=="3.9" # Last version with Python 3.9 suppor
python-gitlab==8.3.0;python_version>="3.10" # Most recent version on 2026-05-04
Waitress>=3.0.1 # required to fix vulnerabilities detected by scorecards
cryptography>=44.0.1 # required to fix vulnerabilities detected by scorecards
PyGHee @ git+https://github.com/boegel/PyGHee.git@c5e10632a45db5ca94f5cbf87ac7a90a2064e8fd # Pin commit with GL support
PyGHee @ git+https://github.com/boegel/PyGHee.git@514bdf6b7db1ed2a965ccdabaf45f9e9b1d825b7 # Pin commit with GL support
retry
2 changes: 1 addition & 1 deletion tasks/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -1078,7 +1078,7 @@ def submit_build_jobs(pr, event_info, action_filter, build_params):
pr_comment = create_pr_comment(job, job_id, app_name, pr, symlink, build_params)
job_id_to_comment_map[job_id] = pr_comment

pr_comment = pr_comments.PRComment(pr.base.repo.full_name, pr.number, pr_comment.id)
pr_comment = pr_comments.PRCommentInfo(pr.base.repo.full_name, pr.number, pr_comment.id)

# create _bot_job<jobid>.metadata file in the job's working directory
job_metadata.create_metadata_file(job, job_id, pr_comment)
Expand Down
3 changes: 3 additions & 0 deletions tests/test_app.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

# sample config file for tests (some functions run config.read_config()
# which reads app.cfg by default)
[git]
hosting_platform = github

[buildenv]
job_handover_protocol = hold_release

Expand Down
4 changes: 2 additions & 2 deletions tests/test_task_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from tools import run_cmd, run_subprocess
from tools.build_params import EESSIBotBuildParams
from tools.job_metadata import create_metadata_file, read_metadata_file
from tools.pr_comments import PRComment, get_submitted_job_comment
from tools.pr_comments import PRCommentInfo, get_submitted_job_comment

# Local tests imports (reusing code from other tests)
from tests.test_tools_pr_comments import MockIssueComment
Expand Down Expand Up @@ -462,7 +462,7 @@ def test_create_read_metadata_file(mocked_github, tmp_path):
job_id = "123"

repo_name = "test_repo"
pr_comment = PRComment(repo_name, pr_number, 77)
pr_comment = PRCommentInfo(repo_name, pr_number, 77)
create_metadata_file(job, job_id, pr_comment)

expected_file = f"_bot_job{job_id}.metadata"
Expand Down
23 changes: 23 additions & 0 deletions tools/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,29 @@
# Local application imports (anything from EESSI/eessi-bot-software-layer)
from tools.filter import EESSIBotActionFilter, EESSIBotActionFilterError
from tools.build_params import EESSIBotBuildParams
from tools.git import get_git_hosting_platform, GITHUB, GITLAB


ALL_COMMANDS = ["help", "build", "show_config", "status", "cancel"]
SUPPORTED_COMMANDS_PER_GIT_HOST = {
GITHUB: ["help", "build", "show_config", "status", "cancel"],
GITLAB: ["help"],
}


def get_supported_commands(cfg=None):
"""
Returns the supported commands for the configured Git hosting platform.

Args:
cfg (ConfigParser): Instance of ConfigParser containing the configuration.
May be passed by caller to avoid re-reading the configuration file.

Returns:
supported_commands (list of strings): The supported commands
"""
git_host = get_git_hosting_platform(cfg)
return SUPPORTED_COMMANDS_PER_GIT_HOST[git_host]


def contains_any_bot_command(body):
Expand Down
19 changes: 12 additions & 7 deletions tools/event_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

# Standard library imports
from functools import cached_property
from typing import Union

# Third party imports (anything installed into the local Python environment)
# (none)
Expand Down Expand Up @@ -252,10 +253,10 @@ def event_type(self):
# We therefore need to check what type of comment it is to get the issue numbers and URLs.
@cached_property
def issue_number(self):
notable_type = self._object_attributes["notable_type"]
if notable_type == "MergeRequest":
noteable_type = self._object_attributes["noteable_type"]
if noteable_type == "MergeRequest":
issue_iid = self._request_body["merge_request"]["iid"]
elif notable_type == "Issue":
elif noteable_type == "Issue":
issue_iid = self._request_body["issue"]["iid"]
else:
# Comments may also come from commits etc. - default to -1
Expand All @@ -264,10 +265,10 @@ def issue_number(self):

@cached_property
def issue_url(self):
notable_type = self._object_attributes["notable_type"]
if notable_type == "MergeRequest":
noteable_type = self._object_attributes["noteable_type"]
if noteable_type == "MergeRequest":
issue_url = self._request_body["merge_request"]["url"]
elif notable_type == "Issue":
elif noteable_type == "Issue":
issue_url = self._request_body["issue"]["url"]
else:
# Comments may also come from commits etc. - default to empty string
Expand Down Expand Up @@ -318,6 +319,10 @@ def repo_name(self):
return self._request_body["project"]["path_with_namespace"]


# Type for subclasses of BaseEventInfo
EventInfo = Union[GitHubEventInfo, GitLabEventInfo]


def create_event_info_instance(event_info):
"""
Creates an EventInfo instance for the configured Git hosting platform.
Expand All @@ -326,7 +331,7 @@ def create_event_info_instance(event_info):
event_info (dict): The event info dictionary created by PyGHee

Returns:
Instance of BaseEventInfo subclass
EventInfo instance or None
"""
git_host = get_git_hosting_platform()
if git_host == GITHUB:
Expand Down
22 changes: 22 additions & 0 deletions tools/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,25 @@ def connect_to_git_hosting_platform():
gitlab.connect()
else:
logging.error(f"Git host not supported: '{git_host}'")


# TODO: We might consider merging these settings later, for example as an 'app_name' setting in the 'git' section
def get_app_name(cfg=None):
"""
Get the configured app/bot name.

Args:
cfg (ConfigParser): Instance of ConfigParser containing the configuration.
May be passed by caller to avoid re-reading the configuration file.

Returns:
(str): The configured app/bot name or None
"""
if not cfg:
cfg = config.read_config()
git_host = get_git_hosting_platform(cfg)
if git_host == GITHUB:
return cfg.get(config.SECTION_GITHUB, config.GITHUB_SETTING_APP_NAME)
elif git_host == GITLAB:
return cfg.get(config.SECTION_GITLAB, config.GITLAB_SETTING_BOT_NAME)
return None
2 changes: 1 addition & 1 deletion tools/job_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def create_metadata_file(job, job_id, pr_comment):
Args:
job (named tuple): key data about job that has been submitted
job_id (string): id of submitted job
pr_comment (PRComment): contains repo_name, pr_number and pr_comment_id
pr_comment (PRCommentInfo): contains repo_name, pr_number and pr_comment_id

Returns:
None (implicitly)
Expand Down
Loading
Loading