Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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