Source code for infrahouse_toolkit.terraform.githubpr
"""
Module for :py:class:`GitHubPR`.
"""
import warnings
from logging import getLogger
from os import environ
from typing import Union
from github import Github, InputFileContent
from github.GithubException import GithubException
from github.IssueComment import IssueComment
from infrahouse_toolkit.terraform import IHParseError, parse_comment
from infrahouse_toolkit.terraform.backends.tfbackend import TFBackend
LOG = getLogger()
[docs]class GitHubPR:
"""
:py:class:`GitHubPR` represents a pull request on GitHub.
The pull request is identified by a repository name and a pull request number.
To access GitHub the class needs a GitHub token.
All these are input arguments of the class.
:param repo_name: Full repository name. For example, ``infrahouse/infrahouse-toolkit``.
:typo repo_name: str
:param pull_request: Pull request number.
:type pull_request: int
:param github_token: GitHub personal access tokens. They are created in
https://github.com/settings/tokens
:type github_token: str
"""
def __init__(self, repo_name: str, pull_request: int, github_token: str = None):
self._github_token = github_token
self._repo_name = repo_name
self._pr_number = pull_request
@property
def comments(self):
"""
An interator with comments in this PR.
"""
return self.pull_request.get_issue_comments()
@property
def github(self):
"""
GitHub client.
"""
return Github(login_or_token=self.github_token)
@property
def github_token(self):
"""
GitHub token as passed by the class argument or from the ``GITHUB_TOKEN`` environment variable.
If the ``GITHUB_TOKEN`` environment variable is not defined the property will return None.
"""
return self._github_token if self._github_token else environ.get("GITHUB_TOKEN")
@property
def repo(self):
"""
Repository object of the repository name passed in the class argument.
"""
return self.github.get_repo(self._repo_name)
@property
def pull_request(self):
"""
Pull request object of the repository name passed in the class argument.
"""
return self.repo.get_pull(self._pr_number)
[docs] def delete_my_comments(self):
"""
Delete all comments in the pull request.
"""
for comment in self.comments:
comment.delete()
[docs] def find_comment_by_backend(self, backend: TFBackend) -> Union[IssueComment, None]:
"""
Find a comment that describes state of a given backend.
It will return None if nothing is found.
:param backend: Terraform Backend configuration.
:return: a comment object or None.
:rtype: IssueComment, None
"""
for comment in self.comments:
try:
status = parse_comment(comment.body)
if status.backend == backend:
return comment
except IHParseError:
pass
return None
[docs] def edit_comment(self, comment: IssueComment, new_text: str, private_gist: bool = True):
"""
Modify existing comment. If the new comment is too big,
publish it as a gist.
"""
try:
comment.edit(new_text)
except GithubException as err:
LOG.error(err)
# https://docs.github.com/en/rest/issues/comments?apiVersion=2022-11-28#create-an-issue-comment
if err.status == 422: # Validation failed, or the endpoint has been spammed.
gist = self._publish_gist(
f"pr-{self._pr_number}-plan",
f"{self._repo_name.replace('/', '-')}-pr-{self._pr_number}-plan.txt",
comment,
not private_gist,
)
comment.edit(f"Comment was too big. It's published as a gist at {gist.html_url}.")
else:
raise
[docs] def publish_comment(self, comment: str, private_gist: bool = None):
"""Add the given text as a comment in the pull request."""
if private_gist is not None:
warnings.warn(
"Argument private_gist is deprecated and will be removed soon.", DeprecationWarning, stacklevel=2
)
try:
self.pull_request.create_issue_comment(comment)
except GithubException as err:
LOG.error(err)
# https://docs.github.com/en/rest/issues/comments?apiVersion=2022-11-28#create-an-issue-comment
if err.status == 422: # Validation failed, or the endpoint has been spammed.
self.pull_request.create_issue_comment("Comment was too big. Check the CI workflow output.")
else:
raise
def _publish_gist(self, gist_id, filename, content, public):
current_user = self.github.get_user()
return current_user.create_gist(
public=public,
files={
gist_id: InputFileContent(
content=content,
new_name=filename,
)
},
)