This repository has been archived by the owner on Oct 13, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Comment on a PR after build is completed and if its not a `dry_run`. This feature is disabled by default and can be enabled by using the `--comment-on-pr` flag. Workflow: - After a build is completed, we get the PR from the merge commit and check if the same comment has already been posted. - If it hasn't it will post the comment. - If the code fails, logger will post an error, but will not hinder other tasks, since enclosed in try-catch block Unit tests added.
- Loading branch information
1 parent
f4e5a90
commit 9cbf532
Showing
5 changed files
with
214 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
from ghapi.all import GhApi | ||
from doozerlib.pushd import Dir | ||
from dockerfile_parse import DockerfileParser | ||
|
||
|
||
class CommentOnPr: | ||
def __init__(self, distgit_dir: str, token: str): | ||
self.distgit_dir = distgit_dir | ||
self.token = token | ||
self.owner = None | ||
self.repo = None | ||
self.commit = None | ||
self.gh_client = None # GhApi client | ||
self.pr_url = None | ||
self.pr_no = None | ||
|
||
def list_comments(self): | ||
""" | ||
List the comments in a PR | ||
""" | ||
# https://docs.github.com/rest/reference/issues#list-issue-comments | ||
return self.gh_client.issues.list_comments(issue_number=self.pr_no, per_page=100) | ||
|
||
def check_if_comment_exist(self, comment): | ||
""" | ||
Check if the same comment already exists in the PR | ||
""" | ||
issue_comments = self.list_comments() | ||
for issue_comment in issue_comments: | ||
if issue_comment["body"] == comment: | ||
return True | ||
return False | ||
|
||
def post_comment(self, comment): | ||
""" | ||
Post the comment in the PR if the comment doesn't exist already | ||
""" | ||
# https://docs.github.com/rest/reference/issues#create-an-issue-comment | ||
if not self.check_if_comment_exist(comment): | ||
self.gh_client.issues.create_comment(issue_number=self.pr_no, body=comment) | ||
return True | ||
return False | ||
|
||
def set_pr_from_commit(self): | ||
""" | ||
Get the PR from the merge commit | ||
""" | ||
# https://docs.github.com/rest/commits/commits#list-pull-requests-associated-with-a-commit | ||
prs = self.gh_client.repos.list_pull_requests_associated_with_commit(self.commit) | ||
if len(prs) == 1: | ||
# self._logger.info(f"PR from merge commit {sha}: {pull_url}") | ||
self.pr_url = prs[0]["html_url"] | ||
self.pr_no = prs[0]["number"] | ||
return | ||
raise Exception(f"Multiple PRs found for merge commit {self.commit}") | ||
|
||
def set_repo_details(self): | ||
""" | ||
Get the owner, commit and repo from the dfp label | ||
""" | ||
with Dir(self.distgit_dir): | ||
dfp = DockerfileParser(str(Dir.getpath().joinpath('Dockerfile'))) | ||
|
||
# eg: "https://github.com/openshift/origin/commit/660e0c785a2c9b1fd5fad33cbcffd77a6d84ccb5" | ||
source_commit_url = dfp.labels["io.openshift.build.commit.url"] | ||
url_split = source_commit_url.split("/") | ||
commit = url_split[-1] # eg: 660e0c785a2c9b1fd5fad33cbcffd77a6d84ccb5 | ||
repo = url_split[-3] # eg: origin | ||
owner = url_split[-4] # eg: openshift | ||
|
||
self.owner = owner | ||
self.commit = commit | ||
self.repo = repo | ||
|
||
def set_github_client(self): | ||
""" | ||
Set the gh client after the get_source_details function is run | ||
""" | ||
self.gh_client = GhApi(owner=self.owner, repo=self.repo, token=self.token) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import unittest | ||
from unittest.mock import MagicMock, patch | ||
from doozerlib.comment_on_pr import CommentOnPr | ||
|
||
|
||
class TestCommentOnPr(unittest.TestCase): | ||
def setUp(self): | ||
self.distgit_dir = "distgit_dir" | ||
self.token = "token" | ||
self.commit = "commit_sha" | ||
self.comment = "comment" | ||
|
||
def test_list_comments(self): | ||
pr_no = 1 | ||
api_mock = MagicMock() | ||
api_mock.issues.list_comments.return_value = [{"body": "test comment"}] | ||
comment_on_pr = CommentOnPr(self.distgit_dir, self.token) | ||
comment_on_pr.pr_no = pr_no | ||
comment_on_pr.gh_client = api_mock | ||
result = comment_on_pr.list_comments() | ||
api_mock.issues.list_comments.assert_called_once_with(issue_number=pr_no, per_page=100) | ||
self.assertEqual(result, [{"body": "test comment"}]) | ||
|
||
@patch.object(CommentOnPr, "list_comments") | ||
def test_check_if_comment_exist(self, mock_list_comments): | ||
api_mock = MagicMock() | ||
api_mock.issues.list_comments.return_value = [{"body": self.comment}] | ||
mock_list_comments.return_value = api_mock.issues.list_comments() | ||
comment_on_pr = CommentOnPr(self.distgit_dir, self.token) | ||
result = comment_on_pr.check_if_comment_exist(self.comment) | ||
self.assertTrue(result) | ||
|
||
@patch.object(CommentOnPr, "list_comments") | ||
def test_check_if_comment_exist_when_comment_does_not_exist(self, mock_list_comments): | ||
api_mock = MagicMock() | ||
api_mock.issues.list_comments.return_value = [{"body": "test comment"}] | ||
mock_list_comments.return_value = api_mock.issues.list_comments() | ||
comment_on_pr = CommentOnPr(self.distgit_dir, self.token) | ||
result = comment_on_pr.check_if_comment_exist(self.comment) | ||
self.assertFalse(result) | ||
|
||
@patch.object(CommentOnPr, "check_if_comment_exist") | ||
def test_post_comment(self, mock_check_if_comment_exist): | ||
pr_no = 1 | ||
api_mock = MagicMock() | ||
mock_check_if_comment_exist.return_value = False | ||
comment_on_pr = CommentOnPr(self.distgit_dir, self.token) | ||
comment_on_pr.pr_no = pr_no | ||
comment_on_pr.gh_client = api_mock | ||
result = comment_on_pr.post_comment(self.comment) | ||
api_mock.issues.create_comment.assert_called_once_with(issue_number=pr_no, body=self.comment) | ||
self.assertTrue(result) | ||
|
||
@patch.object(CommentOnPr, "check_if_comment_exist") | ||
def test_post_comment_when_comment_already_exists(self, mock_check_if_comment_exist): | ||
pr_no = 1 | ||
api_mock = MagicMock() | ||
mock_check_if_comment_exist.return_value = True | ||
comment_on_pr = CommentOnPr(self.distgit_dir, self.token) | ||
comment_on_pr.pr_no = pr_no | ||
comment_on_pr.gh_client = api_mock | ||
result = comment_on_pr.post_comment(self.comment) | ||
api_mock.issues.create_comment.assert_not_called() | ||
self.assertFalse(result) | ||
|
||
def test_get_pr_from_commit(self): | ||
api_mock = MagicMock() | ||
comment_on_pr = CommentOnPr(self.distgit_dir, self.token) | ||
comment_on_pr.gh_client = api_mock | ||
api_mock.repos.list_pull_requests_associated_with_commit.return_value = [{"html_url": "test_url", "number": 1}] | ||
comment_on_pr.set_pr_from_commit() | ||
self.assertEqual(comment_on_pr.pr_url, 'test_url') | ||
self.assertEqual(comment_on_pr.pr_no, 1) | ||
|
||
def test_multiple_prs_for_merge_commit(self): | ||
api_mock = MagicMock() | ||
comment_on_pr = CommentOnPr(self.distgit_dir, self.token) | ||
comment_on_pr.gh_client = api_mock | ||
api_mock.repos.list_pull_requests_associated_with_commit.return_value = [{"html_url": "test_url", "number": 1}, | ||
{"html_url": "test_url_2", | ||
"number": 2}] | ||
with self.assertRaises(Exception): | ||
comment_on_pr.set_pr_from_commit() | ||
|
||
def test_set_github_client(self): | ||
comment_on_pr = CommentOnPr(self.distgit_dir, self.token) | ||
comment_on_pr.owner = "owner" | ||
comment_on_pr.repo = "repo" | ||
comment_on_pr.token = "token" | ||
comment_on_pr.set_github_client() | ||
self.assertIsNotNone(comment_on_pr.gh_client) | ||
|
||
@patch('doozerlib.comment_on_pr.DockerfileParser') | ||
def test_get_source_details(self, mock_parser): | ||
comment_on_pr = CommentOnPr(self.distgit_dir, self.token) | ||
# Mocking the labels dictionary of the DockerfileParser object | ||
mock_parser.return_value.labels = { | ||
"io.openshift.build.commit.url": "https://github.com/openshift/origin/commit/660e0c785a2c9b1fd5fad33cbcffd77a6d84ccb5" | ||
} | ||
|
||
# Calling the get_source_details method | ||
comment_on_pr.set_repo_details() | ||
|
||
# Asserting that the owner, commit, and repo attributes are set correctly | ||
self.assertEqual(comment_on_pr.owner, 'openshift') | ||
self.assertEqual(comment_on_pr.commit, '660e0c785a2c9b1fd5fad33cbcffd77a6d84ccb5') | ||
self.assertEqual(comment_on_pr.repo, 'origin') |