Skip to content

Commit be61061

Browse files
authored
Merge pull request #376 from sondrebr/add-gitlab-support-bot-help-command
[gitlab] Add PRComment classes and GitLab support for `bot: help` command
2 parents 6d7a44c + d09349e commit be61061

10 files changed

Lines changed: 318 additions & 42 deletions

File tree

eessi_bot_event_handler.py

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@
3636
from tools import config
3737
from tools.args import event_handler_parse
3838
from tools.commands import EESSIBotCommand, EESSIBotCommandError, \
39-
contains_any_bot_command, get_bot_command
39+
contains_any_bot_command, get_bot_command, get_supported_commands, ALL_COMMANDS
4040
from tools.event_info import create_event_info_instance
41-
from tools.git import connect_to_git_hosting_platform, get_git_hosting_platform
41+
from tools.git import connect_to_git_hosting_platform, get_app_name, get_git_hosting_platform
4242
from tools.permissions import check_command_permission
4343
from tools.pr_comments import ChatLevels, create_comment
4444

@@ -188,7 +188,7 @@ def handle_issue_comment_event(self, event_info, log_file=None):
188188
comments for any bot command and execute it if one is found.
189189
190190
Args:
191-
event_info (dict): event received by event_handler
191+
event_info (EventInfo): event received by event_handler
192192
log_file (string): path to log messages to
193193
194194
Returns:
@@ -197,27 +197,26 @@ def handle_issue_comment_event(self, event_info, log_file=None):
197197
Raises:
198198
Exception: raises any exception that is not of type EESSIBotCommandError
199199
"""
200-
request_body = event_info['raw_request_body']
201-
issue_url = request_body['issue']['url']
202-
action = request_body['action']
203-
sender = request_body['sender']['login']
204-
owner = request_body['comment']['user']['login']
205-
repo_name = request_body['repository']['full_name']
206-
pr_number = request_body['issue']['number']
200+
issue_url = event_info.issue_url
201+
action = event_info.action
202+
sender = event_info.event_triggered_by
203+
owner = event_info.comment_created_by
204+
repo_name = event_info.repo_name
205+
pr_number = event_info.issue_number
207206

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

212-
app_name = self.cfg[config.SECTION_GITHUB][config.GITHUB_SETTING_APP_NAME]
211+
app_name = get_app_name(self.cfg)
213212
command_response_fmt = self.cfg[config.SECTION_BOT_CONTROL][config.BOT_CONTROL_SETTING_COMMAND_RESPONSE_FMT]
214213

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

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

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

370+
# PyGHee gets the event type by subscripting event_info, i.e., it gets 'note' for GL comment events
371+
handle_note_event = handle_issue_comment_event
372+
371373
def handle_installation_event(self, event_info, log_file=None):
372374
"""
373375
Handle events of type installation. Main action is to log the event.
@@ -497,7 +499,7 @@ def handle_bot_command(self, event_info, bot_command, log_file=None):
497499
specific bot_command given.
498500
499501
Args:
500-
event_info (dict): event received by event_handler
502+
event_info (EventInfo): event received by event_handler
501503
bot_command (EESSIBotCommand): command to be handled
502504
log_file (string): path to log messages to
503505
@@ -512,9 +514,13 @@ def handle_bot_command(self, event_info, bot_command, log_file=None):
512514
cmd = bot_command.command
513515
handler_name = f"handle_bot_command_{cmd}"
514516
if hasattr(self, handler_name):
515-
handler = getattr(self, handler_name)
516-
self.log(f"Handling bot command {cmd}")
517-
return handler(event_info, bot_command)
517+
if cmd in get_supported_commands(self.cfg):
518+
handler = getattr(self, handler_name)
519+
self.log(f"Handling bot command {cmd}")
520+
return handler(event_info, bot_command)
521+
else:
522+
self.log(f"Command '{cmd}' is not supported on the configured Git hosting platform.")
523+
raise EESSIBotCommandError(f"Unsupported command `{cmd}`; use `bot: help` for usage information")
518524
else:
519525
self.log(f"No handler for command '{cmd}'")
520526
raise EESSIBotCommandError(f"unknown command `{cmd}`; use `bot: help` for usage information")
@@ -525,17 +531,25 @@ def handle_bot_command_help(self, event_info, bot_command):
525531
commands.
526532
527533
Args:
528-
event_info (dict): event received by event_handler
534+
event_info (EventInfo): event received by event_handler
529535
bot_command (EESSIBotCommand): command to be handled
530536
531537
Returns:
532538
(string): basic information about sending commands to the bot
533539
"""
540+
# Create comma-separated lists of supported and unsupported commands
541+
supported_commands = get_supported_commands(self.cfg)
542+
unsupported_commands = [cmd for cmd in ALL_COMMANDS if cmd not in supported_commands]
543+
supported_commands_str = ", ".join([f"`{cmd}`" for cmd in supported_commands])
544+
unsupported_commands_str = ", ".join([f"`{cmd}`" for cmd in unsupported_commands])
545+
534546
help_msg = "\n **How to send commands to bot instances**"
535547
help_msg += "\n - Commands must be sent with a **new** comment (edits of existing comments are ignored)."
536548
help_msg += "\n - A comment may contain multiple commands, one per line."
537549
help_msg += "\n - Every command begins at the start of a line and has the syntax `bot: COMMAND [ARGUMENTS]*`"
538-
help_msg += "\n - Currently supported COMMANDs are: `help`, `build`, `show_config`, `status`, `cancel`"
550+
help_msg += "\n - Currently supported COMMANDs are: " + supported_commands_str
551+
if unsupported_commands_str:
552+
help_msg += "\n - The following COMMANDs are not yet supported: " + unsupported_commands_str
539553
help_msg += "\n"
540554
help_msg += "\n For more information, see https://www.eessi.io/docs/bot"
541555
return help_msg
@@ -572,7 +586,7 @@ def handle_bot_command_build(self, event_info, bot_command):
572586
else:
573587
for job_id, issue_comment in submitted_jobs.items():
574588
build_msg += f"\n - submitted job `{job_id}`"
575-
if issue_comment:
589+
if issue_comment and issue_comment.html_url:
576590
build_msg += f", for details & status see {issue_comment.html_url}"
577591
else:
578592
request_body = event_info['raw_request_body']

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ python-gitlab==6.5.0;python_version=="3.9" # Last version with Python 3.9 suppor
1616
python-gitlab==8.3.0;python_version>="3.10" # Most recent version on 2026-05-04
1717
Waitress>=3.0.1 # required to fix vulnerabilities detected by scorecards
1818
cryptography>=44.0.1 # required to fix vulnerabilities detected by scorecards
19-
PyGHee @ git+https://github.com/boegel/PyGHee.git@c5e10632a45db5ca94f5cbf87ac7a90a2064e8fd # Pin commit with GL support
19+
PyGHee @ git+https://github.com/boegel/PyGHee.git@514bdf6b7db1ed2a965ccdabaf45f9e9b1d825b7 # Pin commit with GL support
2020
retry

tasks/build.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1078,7 +1078,7 @@ def submit_build_jobs(pr, event_info, action_filter, build_params):
10781078
pr_comment = create_pr_comment(job, job_id, app_name, pr, symlink, build_params)
10791079
job_id_to_comment_map[job_id] = pr_comment
10801080

1081-
pr_comment = pr_comments.PRComment(pr.base.repo.full_name, pr.number, pr_comment.id)
1081+
pr_comment = pr_comments.PRCommentInfo(pr.base.repo.full_name, pr.number, pr_comment.id)
10821082

10831083
# create _bot_job<jobid>.metadata file in the job's working directory
10841084
job_metadata.create_metadata_file(job, job_id, pr_comment)

tests/test_app.cfg

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111

1212
# sample config file for tests (some functions run config.read_config()
1313
# which reads app.cfg by default)
14+
[git]
15+
hosting_platform = github
16+
1417
[buildenv]
1518
job_handover_protocol = hold_release
1619

tests/test_task_build.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from tools import run_cmd, run_subprocess
3131
from tools.build_params import EESSIBotBuildParams
3232
from tools.job_metadata import create_metadata_file, read_metadata_file
33-
from tools.pr_comments import PRComment, get_submitted_job_comment
33+
from tools.pr_comments import PRCommentInfo, get_submitted_job_comment
3434

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

464464
repo_name = "test_repo"
465-
pr_comment = PRComment(repo_name, pr_number, 77)
465+
pr_comment = PRCommentInfo(repo_name, pr_number, 77)
466466
create_metadata_file(job, job_id, pr_comment)
467467

468468
expected_file = f"_bot_job{job_id}.metadata"

tools/commands.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,29 @@
1919
# Local application imports (anything from EESSI/eessi-bot-software-layer)
2020
from tools.filter import EESSIBotActionFilter, EESSIBotActionFilterError
2121
from tools.build_params import EESSIBotBuildParams
22+
from tools.git import get_git_hosting_platform, GITHUB, GITLAB
23+
24+
25+
ALL_COMMANDS = ["help", "build", "show_config", "status", "cancel"]
26+
SUPPORTED_COMMANDS_PER_GIT_HOST = {
27+
GITHUB: ["help", "build", "show_config", "status", "cancel"],
28+
GITLAB: ["help"],
29+
}
30+
31+
32+
def get_supported_commands(cfg=None):
33+
"""
34+
Returns the supported commands for the configured Git hosting platform.
35+
36+
Args:
37+
cfg (ConfigParser): Instance of ConfigParser containing the configuration.
38+
May be passed by caller to avoid re-reading the configuration file.
39+
40+
Returns:
41+
supported_commands (list of strings): The supported commands
42+
"""
43+
git_host = get_git_hosting_platform(cfg)
44+
return SUPPORTED_COMMANDS_PER_GIT_HOST[git_host]
2245

2346

2447
def contains_any_bot_command(body):

tools/event_info.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
# Standard library imports
1313
from functools import cached_property
14+
from typing import Union
1415

1516
# Third party imports (anything installed into the local Python environment)
1617
# (none)
@@ -252,10 +253,10 @@ def event_type(self):
252253
# We therefore need to check what type of comment it is to get the issue numbers and URLs.
253254
@cached_property
254255
def issue_number(self):
255-
notable_type = self._object_attributes["notable_type"]
256-
if notable_type == "MergeRequest":
256+
noteable_type = self._object_attributes["noteable_type"]
257+
if noteable_type == "MergeRequest":
257258
issue_iid = self._request_body["merge_request"]["iid"]
258-
elif notable_type == "Issue":
259+
elif noteable_type == "Issue":
259260
issue_iid = self._request_body["issue"]["iid"]
260261
else:
261262
# Comments may also come from commits etc. - default to -1
@@ -264,10 +265,10 @@ def issue_number(self):
264265

265266
@cached_property
266267
def issue_url(self):
267-
notable_type = self._object_attributes["notable_type"]
268-
if notable_type == "MergeRequest":
268+
noteable_type = self._object_attributes["noteable_type"]
269+
if noteable_type == "MergeRequest":
269270
issue_url = self._request_body["merge_request"]["url"]
270-
elif notable_type == "Issue":
271+
elif noteable_type == "Issue":
271272
issue_url = self._request_body["issue"]["url"]
272273
else:
273274
# Comments may also come from commits etc. - default to empty string
@@ -318,6 +319,10 @@ def repo_name(self):
318319
return self._request_body["project"]["path_with_namespace"]
319320

320321

322+
# Type for subclasses of BaseEventInfo
323+
EventInfo = Union[GitHubEventInfo, GitLabEventInfo]
324+
325+
321326
def create_event_info_instance(event_info):
322327
"""
323328
Creates an EventInfo instance for the configured Git hosting platform.
@@ -326,7 +331,7 @@ def create_event_info_instance(event_info):
326331
event_info (dict): The event info dictionary created by PyGHee
327332
328333
Returns:
329-
Instance of BaseEventInfo subclass
334+
EventInfo instance or None
330335
"""
331336
git_host = get_git_hosting_platform()
332337
if git_host == GITHUB:

tools/git.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,25 @@ def connect_to_git_hosting_platform():
7171
gitlab.connect()
7272
else:
7373
logging.error(f"Git host not supported: '{git_host}'")
74+
75+
76+
# TODO: We might consider merging these settings later, for example as an 'app_name' setting in the 'git' section
77+
def get_app_name(cfg=None):
78+
"""
79+
Get the configured app/bot name.
80+
81+
Args:
82+
cfg (ConfigParser): Instance of ConfigParser containing the configuration.
83+
May be passed by caller to avoid re-reading the configuration file.
84+
85+
Returns:
86+
(str): The configured app/bot name or None
87+
"""
88+
if not cfg:
89+
cfg = config.read_config()
90+
git_host = get_git_hosting_platform(cfg)
91+
if git_host == GITHUB:
92+
return cfg.get(config.SECTION_GITHUB, config.GITHUB_SETTING_APP_NAME)
93+
elif git_host == GITLAB:
94+
return cfg.get(config.SECTION_GITLAB, config.GITLAB_SETTING_BOT_NAME)
95+
return None

tools/job_metadata.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def create_metadata_file(job, job_id, pr_comment):
9090
Args:
9191
job (named tuple): key data about job that has been submitted
9292
job_id (string): id of submitted job
93-
pr_comment (PRComment): contains repo_name, pr_number and pr_comment_id
93+
pr_comment (PRCommentInfo): contains repo_name, pr_number and pr_comment_id
9494
9595
Returns:
9696
None (implicitly)

0 commit comments

Comments
 (0)