Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

release v0.3.0 #240

Merged
merged 21 commits into from
Jan 30, 2024
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
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
Loading