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
2 changes: 2 additions & 0 deletions app.cfg.example
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ command_response_fmt =
{comment_result}
</details>

# chattiness level of the bot in terms of writing comments into PRs (minimal, basic, or chatty)
chatlevel = basic

[buildenv]
# name of the job script used for building an EESSI stack
Expand Down
60 changes: 31 additions & 29 deletions eessi_bot_event_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,14 @@
from tools.commands import EESSIBotCommand, EESSIBotCommandError, \
contains_any_bot_command, get_bot_command
from tools.permissions import check_command_permission
from tools.pr_comments import create_comment
from tools.pr_comments import ChatLevels, create_comment


REQUIRED_CONFIG = {
config.SECTION_ARCHITECTURETARGETS: [
config.ARCHITECTURETARGETS_SETTING_ARCH_TARGET_MAP], # required
config.SECTION_BOT_CONTROL: [
# config.BOT_CONTROL_SETTING_CHATLEVEL, # optional
config.BOT_CONTROL_SETTING_COMMAND_PERMISSION, # required
config.BOT_CONTROL_SETTING_COMMAND_RESPONSE_FMT], # required
config.SECTION_BUILDENV: [
Expand Down Expand Up @@ -193,6 +194,8 @@ def handle_issue_comment_event(self, event_info, log_file=None):
return
# at this point we know that we are handling a new comment

issue_comment = None

# check if comment does not contain a bot command
if not contains_any_bot_command(comment_received):
self.log("comment does not contain a bot comment; not processing it further")
Expand Down Expand Up @@ -229,7 +232,7 @@ def handle_issue_comment_event(self, event_info, log_file=None):
comment_response=comment_response,
comment_result=''
)
issue_comment = create_comment(repo_name, pr_number, comment_body)
issue_comment = create_comment(repo_name, pr_number, comment_body, ChatLevels.CHATTY)
else:
self.log(f"account `{sender}` seems to be a bot instance itself, hence not creating a new PR comment")
return
Expand Down Expand Up @@ -263,6 +266,11 @@ def handle_issue_comment_event(self, event_info, log_file=None):
# including a bot command; the logging should only be done when log
# level is set to debug

if 'help' in (x.command for x in commands):
req_chatlevel = ChatLevels.MINIMAL
else:
req_chatlevel = ChatLevels.CHATTY

if comment_response == '':
# no update to be added, just log and return
self.log("comment response is empty")
Expand All @@ -281,7 +289,7 @@ def handle_issue_comment_event(self, event_info, log_file=None):
comment_response=comment_response,
comment_result=''
)
issue_comment = create_comment(repo_name, pr_number, comment_body)
issue_comment = create_comment(repo_name, pr_number, comment_body, req_chatlevel)
else:
self.log(f"update '{comment_response}' is considered to contain bot command ... not creating PR comment")
# TODO we may want to report this back to the PR on GitHub, e.g.,
Expand All @@ -306,24 +314,26 @@ def handle_issue_comment_event(self, event_info, log_file=None):
continue
except Exception as err:
log(f"Unexpected err={err}, type(err)={type(err)}")
if comment_result:
if comment_result and issue_comment:
comment_body = command_response_fmt.format(
app_name=app_name,
comment_response=comment_response,
comment_result=comment_result
)
issue_comment.edit(comment_body)
raise
# only update PR comment once, that is, a single call to
# issue_comment.edit is made in the entire function
comment_body = command_response_fmt.format(
app_name=app_name,
comment_response=comment_response,
comment_result=comment_result
)
issue_comment.edit(comment_body)

self.log(f"issue_comment event (url {issue_url}) handled!")
if issue_comment:
# only update PR comment once, that is, a single call to
# issue_comment.edit is made in the entire function
comment_body = command_response_fmt.format(
app_name=app_name,
comment_response=comment_response,
comment_result=comment_result
)
issue_comment.edit(comment_body)

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

def handle_installation_event(self, event_info, log_file=None):
"""
Expand Down Expand Up @@ -373,14 +383,14 @@ def handle_pull_request_labeled_event(self, event_info, pr):
comment_response=msg,
comment_result=''
)
create_comment(repo_name, pr_number, comment_body)
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)
else:
self.log("handle_pull_request_labeled_event: no handler for label '%s'", label)

def handle_pull_request_opened_event(self, event_info, pr):
def handle_pull_request_opened_event(self, event_info, pr, 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
Expand Down Expand Up @@ -420,10 +430,7 @@ def handle_pull_request_opened_event(self, event_info, pr):

# create comment to pull request
repo_name = pr.base.repo.full_name
gh = github.get_instance()
repo = gh.get_repo(repo_name)
pull_request = repo.get_pull(pr.number)
issue_comment = pull_request.create_issue_comment(comment)
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):
Expand Down Expand Up @@ -554,8 +561,9 @@ def handle_bot_command_show_config(self, event_info, bot_command):
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)
return f"\n - added comment {issue_comment.html_url} to show configuration"
issue_comment = self.handle_pull_request_opened_event(event_info, pr, req_chatlevel=ChatLevels.MINIMAL)
if issue_comment:
return f"\n - added comment {issue_comment.html_url} to show configuration"

def handle_bot_command_status(self, event_info, bot_command):
"""
Expand All @@ -571,7 +579,6 @@ def handle_bot_command_status(self, event_info, bot_command):
PyGithub, not the github from the internal connections module)
"""
self.log("processing bot command 'status'")
gh = github.get_instance()
repo_name = event_info['raw_request_body']['repository']['full_name']
pr_number = event_info['raw_request_body']['issue']['number']
status_table = request_bot_build_issue_comments(repo_name, pr_number)
Expand All @@ -588,9 +595,7 @@ def handle_bot_command_status(self, event_info, bot_command):
comment_status += f"{status_table['url'][x]}|"

self.log(f"Overview of finished builds: comment '{comment_status}'")
repo = gh.get_repo(repo_name)
pull_request = repo.get_pull(pr_number)
issue_comment = pull_request.create_issue_comment(comment_status)
issue_comment = create_comment(repo_name, pr_number, comment_status, ChatLevels.MINIMAL)
return issue_comment

def start(self, app, port=3000):
Expand Down Expand Up @@ -669,12 +674,9 @@ def handle_pull_request_closed_event(self, event_info, pr):
# 4) report move to pull request

repo_name = pr.base.repo.full_name
gh = github.get_instance()
repo = gh.get_repo(repo_name)
pull_request = repo.get_pull(pr.number)
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 = pull_request.create_issue_comment(moved_comment)
issue_comment = create_comment(repo_name, pr.number, moved_comment, ChatLevels.CHATTY)
return issue_comment


Expand Down
21 changes: 7 additions & 14 deletions tasks/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,11 @@

# Third party imports (anything installed into the local Python environment)
from pyghee.utils import error, log
from retry.api import retry_call

# Local application imports (anything from EESSI/eessi-bot-software-layer)
from connections import github
from tools import config, cvmfs_repository, job_metadata, pr_comments, run_cmd
import tools.filter as tools_filter
from tools.pr_comments import ChatLevels, create_comment


# defaults (used if not specified via, eg, 'app.cfg')
Expand Down Expand Up @@ -484,7 +483,7 @@ def comment_download_pr(base_repo_name, pr, download_pr_exit_code, download_pr_e
f"\n{download_pr_comments_cfg[config.DOWNLOAD_PR_COMMENTS_SETTING_GIT_APPLY_TIP]}")

download_comment = pr_comments.create_comment(
repo_name=base_repo_name, pr_number=pr.number, comment=download_comment
repo_name=base_repo_name, pr_number=pr.number, comment=download_comment, req_chatlevel=ChatLevels.MINIMAL
)
if download_comment:
log(f"{fn}(): created PR issue comment with id {download_comment.id}")
Expand Down Expand Up @@ -887,7 +886,7 @@ def submit_job(job, cfg):
return job_id, symlink


def create_pr_comment(job, job_id, app_name, pr, gh, symlink):
def create_pr_comment(job, job_id, app_name, pr, symlink):
"""
Create a comment to the pull request for a newly submitted job

Expand All @@ -896,7 +895,6 @@ def create_pr_comment(job, job_id, app_name, pr, gh, symlink):
job_id (string): id of the submitted job
app_name (string): name of the app
pr (github.PullRequest.PullRequest): instance representing the pull request
gh (object): github instance
symlink (string): symlink from main pr_<ID> dir to job dir

Returns:
Expand Down Expand Up @@ -961,10 +959,7 @@ def create_pr_comment(job, job_id, app_name, pr, gh, symlink):

# create comment to pull request
repo_name = pr.base.repo.full_name
repo = gh.get_repo(repo_name)
pull_request = repo.get_pull(pr.number)
issue_comment = retry_call(pull_request.create_issue_comment, fargs=[job_comment],
exceptions=Exception, tries=3, delay=1, backoff=2, max_delay=10)
issue_comment = create_comment(repo_name, pr.number, job_comment, ChatLevels.MINIMAL)
if issue_comment:
log(f"{fn}(): created PR issue comment with id {issue_comment.id}")
return issue_comment
Expand Down Expand Up @@ -1002,9 +997,6 @@ def submit_build_jobs(pr, event_info, action_filter):
log(f"{fn}(): no jobs ({len(jobs)}) to be submitted")
return {}

# obtain handle to GitHub
gh = github.get_instance()

# process prepared jobs: submit, create metadata file and add comment to pull
# request on GitHub
job_id_to_comment_map = {}
Expand All @@ -1013,7 +1005,7 @@ def submit_build_jobs(pr, event_info, action_filter):
job_id, symlink = submit_job(job, cfg)

# create pull request comment to report about the submitted job
pr_comment = create_pr_comment(job, job_id, app_name, pr, gh, symlink)
pr_comment = create_pr_comment(job, job_id, app_name, pr, symlink)
job_id_to_comment_map[job_id] = pr_comment

pr_comment = pr_comments.PRComment(pr.base.repo.full_name, pr.number, pr_comment.id)
Expand Down Expand Up @@ -1056,7 +1048,8 @@ def check_build_permission(pr, event_info):
repo_name = event_info["raw_request_body"]["repository"]["full_name"]
pr_comments.create_comment(repo_name,
pr.number,
no_build_permission_comment.format(build_labeler=build_labeler))
no_build_permission_comment.format(build_labeler=build_labeler),
ChatLevels.MINIMAL)
return False
else:
log(f"{fn}(): GH account '{build_labeler}' is authorized to build")
Expand Down
4 changes: 3 additions & 1 deletion tasks/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from connections import github
from tasks.build import get_build_env_cfg
from tools import config, job_metadata, pr_comments, run_cmd
from tools.pr_comments import ChatLevels


def determine_job_dirs(pr_number):
Expand Down Expand Up @@ -617,7 +618,8 @@ def deploy_built_artefacts(pr, event_info):
repo_name = event_info["raw_request_body"]["repository"]["full_name"]
pr_comments.create_comment(repo_name,
pr.number,
no_deploy_permission_comment.format(deploy_labeler=labeler))
no_deploy_permission_comment.format(deploy_labeler=labeler),
ChatLevels.CHATTY)
return
else:
log(f"{funcname}(): GH account '{labeler}' is authorized to deploy")
Expand Down
2 changes: 2 additions & 0 deletions tests/test_app.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ awaits_lauch = job awaits launch by Slurm scheduler
running_job = job `{job_id}` is running

[finished_job_comments]

[bot_control]
28 changes: 18 additions & 10 deletions tests/test_task_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ def get_repo(self, repo_name):
repo = self.repos[repo_name]
return repo

def get_instance(self):
return self


MockBase = namedtuple('MockBase', ['repo'])

Expand Down Expand Up @@ -275,8 +278,9 @@ def no_sleep_after_create(delay):
# returns !None --> create_pr_comment returns comment (with id == 1)
@pytest.mark.repo_name("EESSI/software-layer")
@pytest.mark.pr_number(1)
def test_create_pr_comment_succeeds(mocked_github, tmpdir):
def test_create_pr_comment_succeeds(monkeypatch, mocked_github, tmpdir):
"""Tests for function create_pr_comment."""
monkeypatch.setattr('tools.pr_comments.github', mocked_github)
shutil.copyfile("tests/test_app.cfg", "app.cfg")
# creating a PR comment
print("CREATING PR COMMENT")
Expand All @@ -291,7 +295,7 @@ def test_create_pr_comment_succeeds(mocked_github, tmpdir):
repo = mocked_github.get_repo(repo_name)
pr = repo.get_pull(pr_number)
symlink = "/symlink"
comment = create_pr_comment(job, job_id, app_name, pr, mocked_github, symlink)
comment = create_pr_comment(job, job_id, app_name, pr, symlink)
assert comment.id == 1
# check if created comment includes jobid?
print("VERIFYING PR COMMENT")
Expand All @@ -304,8 +308,9 @@ def test_create_pr_comment_succeeds(mocked_github, tmpdir):
@pytest.mark.repo_name("EESSI/software-layer")
@pytest.mark.pr_number(1)
@pytest.mark.create_fails(True)
def test_create_pr_comment_succeeds_none(mocked_github, tmpdir):
def test_create_pr_comment_succeeds_none(monkeypatch, mocked_github, tmpdir):
"""Tests for function create_pr_comment."""
monkeypatch.setattr('tools.pr_comments.github', mocked_github)
shutil.copyfile("tests/test_app.cfg", "app.cfg")
# creating a PR comment
print("CREATING PR COMMENT")
Expand All @@ -320,7 +325,7 @@ def test_create_pr_comment_succeeds_none(mocked_github, tmpdir):
repo = mocked_github.get_repo(repo_name)
pr = repo.get_pull(pr_number)
symlink = "/symlink"
comment = create_pr_comment(job, job_id, app_name, pr, mocked_github, symlink)
comment = create_pr_comment(job, job_id, app_name, pr, symlink)
assert comment is None


Expand All @@ -329,8 +334,9 @@ def test_create_pr_comment_succeeds_none(mocked_github, tmpdir):
@pytest.mark.repo_name("EESSI/software-layer")
@pytest.mark.pr_number(1)
@pytest.mark.create_raises("1")
def test_create_pr_comment_raises_once_then_succeeds(mocked_github, tmpdir):
def test_create_pr_comment_raises_once_then_succeeds(monkeypatch, mocked_github, tmpdir):
"""Tests for function create_pr_comment."""
monkeypatch.setattr('tools.pr_comments.github', mocked_github)
shutil.copyfile("tests/test_app.cfg", "app.cfg")
# creating a PR comment
print("CREATING PR COMMENT")
Expand All @@ -345,7 +351,7 @@ def test_create_pr_comment_raises_once_then_succeeds(mocked_github, tmpdir):
repo = mocked_github.get_repo(repo_name)
pr = repo.get_pull(pr_number)
symlink = "/symlink"
comment = create_pr_comment(job, job_id, app_name, pr, mocked_github, symlink)
comment = create_pr_comment(job, job_id, app_name, pr, symlink)
assert comment.id == 1
assert pr.create_call_count == 2

Expand All @@ -354,8 +360,9 @@ def test_create_pr_comment_raises_once_then_succeeds(mocked_github, tmpdir):
@pytest.mark.repo_name("EESSI/software-layer")
@pytest.mark.pr_number(1)
@pytest.mark.create_raises("always_raise")
def test_create_pr_comment_always_raises(mocked_github, tmpdir):
def test_create_pr_comment_always_raises(monkeypatch, mocked_github, tmpdir):
"""Tests for function create_pr_comment."""
monkeypatch.setattr('tools.pr_comments.github', mocked_github)
shutil.copyfile("tests/test_app.cfg", "app.cfg")
# creating a PR comment
print("CREATING PR COMMENT")
Expand All @@ -371,7 +378,7 @@ def test_create_pr_comment_always_raises(mocked_github, tmpdir):
pr = repo.get_pull(pr_number)
symlink = "/symlink"
with pytest.raises(Exception) as err:
create_pr_comment(job, job_id, app_name, pr, mocked_github, symlink)
create_pr_comment(job, job_id, app_name, pr, symlink)
assert err.type == CreateIssueCommentException
assert pr.create_call_count == 3

Expand All @@ -380,8 +387,9 @@ def test_create_pr_comment_always_raises(mocked_github, tmpdir):
@pytest.mark.repo_name("EESSI/software-layer")
@pytest.mark.pr_number(1)
@pytest.mark.create_raises("3")
def test_create_pr_comment_three_raises(mocked_github, tmpdir):
def test_create_pr_comment_three_raises(monkeypatch, mocked_github, tmpdir):
"""Tests for function create_pr_comment."""
monkeypatch.setattr('tools.pr_comments.github', mocked_github)
shutil.copyfile("tests/test_app.cfg", "app.cfg")
# creating a PR comment
print("CREATING PR COMMENT")
Expand All @@ -397,7 +405,7 @@ def test_create_pr_comment_three_raises(mocked_github, tmpdir):
pr = repo.get_pull(pr_number)
symlink = "/symlink"
with pytest.raises(Exception) as err:
create_pr_comment(job, job_id, app_name, pr, mocked_github, symlink)
create_pr_comment(job, job_id, app_name, pr, symlink)
assert err.type == CreateIssueCommentException
assert pr.create_call_count == 3

Expand Down
1 change: 1 addition & 0 deletions tools/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
SECTION_BOT_CONTROL = 'bot_control'
BOT_CONTROL_SETTING_COMMAND_PERMISSION = 'command_permission'
BOT_CONTROL_SETTING_COMMAND_RESPONSE_FMT = 'command_response_fmt'
BOT_CONTROL_SETTING_CHATLEVEL = 'chatlevel'

SECTION_BUILDENV = 'buildenv'
BUILDENV_SETTING_ALLOWED_EXPORTVARS = 'allowed_exportvars'
Expand Down
Loading