Skip to content

Commit

Permalink
Merge pull request #240 from EESSI/develop
Browse files Browse the repository at this point in the history
release v0.3.0
  • Loading branch information
boegel authored Jan 30, 2024
2 parents 66ba1f9 + 91e39ff commit 6ba9625
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 6 deletions.
13 changes: 13 additions & 0 deletions RELEASE_NOTES
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
This file contains a description of the major changes to the EESSI
build-and-deploy bot. For more detailed information, please see the git log.

v0.3.0 (30 January 2024)
--------------------------

This is a minor release of the EESSI build-and-deploy bot.

Bug fixes:
* refreshes the token to access GitHub well before it expires (#238)

Improvements:
* adds a new bot command 'status' which provides an overview (table) of all
finished builds (#237)


v0.2.0 (26 November 2023)
--------------------------

Expand Down
9 changes: 5 additions & 4 deletions connections/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#

# Standard library imports
from datetime import datetime, timezone
from datetime import datetime, timedelta, timezone
import time

# Third party imports (anything installed into the local Python environment)
Expand Down Expand Up @@ -99,8 +99,6 @@ def get_instance():
Instance of Github
"""
global _gh, _token
# TODO Possibly renew token already if expiry date is soon, not only
# after it has expired.

# Check if PyGithub version is < 1.56
if hasattr(github, 'GithubRetry'):
Expand All @@ -110,7 +108,10 @@ def get_instance():
# Pygithub 1.x
time_now = datetime.utcnow()

if not _gh or (_token and time_now > _token.expires_at):
# Renew token already if expiry date is less then 30 min away.
refresh_time = timedelta(minutes=30)

if not _gh or (_token and time_now > (_token.expires_at - refresh_time)):
_gh = connect()
return _gh

Expand Down
41 changes: 39 additions & 2 deletions eessi_bot_event_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
# Local application imports (anything from EESSI/eessi-bot-software-layer)
from connections import github
import tasks.build as build
from tasks.build import check_build_permission, get_architecture_targets, get_repo_cfg, submit_build_jobs
from tasks.build import check_build_permission, get_architecture_targets, get_repo_cfg, \
request_bot_build_issue_comments, submit_build_jobs
import tasks.deploy as deploy
from tasks.deploy import deploy_built_artefacts
from tools import config
Expand Down Expand Up @@ -416,7 +417,7 @@ def handle_bot_command_help(self, event_info, bot_command):
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`"
help_msg += "\n - Currently supported COMMANDs are: `help`, `build`, `show_config`, `status`"
help_msg += "\n"
help_msg += "\n For more information, see https://www.eessi.io/docs/bot"
return help_msg
Expand Down Expand Up @@ -476,6 +477,42 @@ def handle_bot_command_show_config(self, event_info, bot_command):
issue_comment = self.handle_pull_request_opened_event(event_info, pr)
return f"\n - added comment {issue_comment.html_url} to show configuration"

def handle_bot_command_status(self, event_info, bot_command):
"""
Handles bot command 'status' by querying the github API
for the comments in a pr.
Args:
event_info (dict): event received by event_handler
bot_command (EESSIBotCommand): command to be handled
Returns:
github.IssueComment.IssueComment (note, github refers to
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)

comment_status = ''
comment_status += "\nThis is the status of all the `bot: build` commands:"
comment_status += "\n|arch|result|date|status|url|"
comment_status += "\n|----|------|----|------|---|"
for x in range(0, len(status_table['date'])):
comment_status += f"\n|{status_table['arch'][x]}|"
comment_status += f"{status_table['result'][x]}|"
comment_status += f"{status_table['date'][x]}|"
comment_status += f"{status_table['status'][x]}|"
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)
return issue_comment

def start(self, app, port=3000):
"""
Logs startup information to shell and log file and starts the app using
Expand Down
75 changes: 75 additions & 0 deletions tasks/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -760,3 +760,78 @@ def check_build_permission(pr, event_info):
else:
log(f"{fn}(): GH account '{build_labeler}' is authorized to build")
return True


def request_bot_build_issue_comments(repo_name, pr_number):
"""
Query the github API for the issue_comments in a pr.
Archs:
repo_name (string): name of the repository (format USER_OR_ORGANISATION/REPOSITORY)
pr_number (int): number og the pr
Returns:
status_table (dict): dictionary with 'arch', 'date', 'status', 'url' and 'result'
for all the finished builds;
"""
status_table = {'arch': [], 'date': [], 'status': [], 'url': [], 'result': []}
cfg = config.read_config()

# for loop because github has max 100 items per request.
# if the pr has more than 100 comments we need to use per_page
# argument at the moment the for loop is for a max of 400 comments could bump this up
for x in range(1, 5):
curl_cmd = f'curl -L https://api.github.com/repos/{repo_name}/issues/{pr_number}/comments?per_page=100&page={x}'
curl_output, curl_error, curl_exit_code = run_cmd(curl_cmd, "fetch all comments")

comments = json.loads(curl_output)

for comment in comments:
# iterate through the comments to find the one where the status of the build was in
if config.read_config()["submitted_job_comments"]['initial_comment'][:20] in comment['body']:

# get archictecture from comment['body']
first_line = comment['body'].split('\n')[0]
arch_map = get_architecture_targets(cfg)
for arch in arch_map.keys():
target_arch = '/'.join(arch.split('/')[1:])
if target_arch in first_line:
status_table['arch'].append(target_arch)

# get date, status, url and result from the markdown table
comment_table = comment['body'][comment['body'].find('|'):comment['body'].rfind('|')+1]

# Convert markdown table to a dictionary
lines = comment_table.split('\n')
rows = []
keys = []
for i, row in enumerate(lines):
values = {}
if i == 0:
for key in row.split('|'):
keys.append(key.strip())
elif i == 1:
continue
else:
for j, value in enumerate(row.split('|')):
if j > 0 and j < len(keys) - 1:
values[keys[j]] = value.strip()
rows.append(values)

# add date, status, url to status_table if
for row in rows:
if row['job status'] == 'finished':
status_table['date'].append(row['date'])
status_table['status'].append(row['job status'])
status_table['url'].append(comment['html_url'])
if 'FAILURE' in row['comment']:
status_table['result'].append(':cry: FAILURE')
elif 'SUCCESS' in value['comment']:
status_table['result'].append(':grin: SUCCESS')
elif 'UNKNOWN' in row['comment']:
status_table['result'].append(':shrug: UNKNOWN')
else:
status_table['result'].append(row['comment'])
if len(comments) != 100:
break
return status_table

0 comments on commit 6ba9625

Please sign in to comment.