Skip to content
This repository has been archived by the owner on May 16, 2022. It is now read-only.

Commit

Permalink
Merge pull request #231 from marusinm/api-replacement
Browse files Browse the repository at this point in the history
closing issues using ogr
  • Loading branch information
jpopelka authored Jul 16, 2019
2 parents 753f5e2 + cc9a4d6 commit c495449
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 105 deletions.
15 changes: 9 additions & 6 deletions release_bot/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

class Configuration:
# note that required items need to reference strings as their length is checked
REQUIRED_ITEMS = {"conf": ['repository_name', 'repository_owner', 'github_token'],
REQUIRED_ITEMS = {"conf": ['repository_name', 'repository_owner'],
"release-conf": []}

def __init__(self):
Expand All @@ -53,7 +53,7 @@ def __init__(self):
self.github_app_cert_path = ''
self.clone_url = ''
# used for different pagure forges pagure.io by default
self.pagure_instance_url = 'pagure.io'
self.pagure_instance_url = 'https://pagure.io'
self.webhook_handler = False
self.gitchangelog = False
self.project = None
Expand Down Expand Up @@ -143,10 +143,13 @@ def load_release_conf(self, conf):
sys.exit(1)
for index, label in enumerate(parsed_conf.get('labels', [])):
parsed_conf['labels'][index] = str(label)
if parsed_conf.get('trigger_on_issue') and not self.github_username:
msg = "Can't trigger on issue if 'github_username' is not known, disabling"
self.logger.warning(msg)
parsed_conf['trigger_on_issue'] = False

if parsed_conf.get('trigger_on_issue'):
if not self.github_username and not self.pagure_username:
msg = ("Can't trigger on issue if "
"'github_username/pagure_username' is not known, disabling")
self.logger.warning(msg)
parsed_conf['trigger_on_issue'] = False

return parsed_conf

Expand Down
96 changes: 28 additions & 68 deletions release_bot/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import jwt
import requests


logger = logging.getLogger('release-bot')


Expand All @@ -37,12 +36,12 @@ def __init__(self, iss, key, expiration=10 * 60):
def generate_token(self):
# Generate the JWT
payload = {
# issued at time
'iat': int(time.time()),
# JWT expiration time (10 minute maximum)
'exp': int(time.time()) + self.expiration,
# GitHub App's identifier
'iss': self.iss
# issued at time
'iat': int(time.time()),
# JWT expiration time (10 minute maximum)
'exp': int(time.time()) + self.expiration,
# GitHub App's identifier
'iss': self.iss
}

tok = jwt.encode(payload, self.key, algorithm='RS256')
Expand Down Expand Up @@ -101,6 +100,7 @@ def __init__(self, configuration, git):
"""
self.conf = configuration
self.logger = configuration.logger
self.project = configuration.project
self.session = requests.Session()
self.session.headers.update({'Authorization': f'token {configuration.github_token}'})
self.github_app_session = None
Expand All @@ -116,7 +116,8 @@ def update_github_app_token(self):
self.logger.debug("github app token obtained")
self.github_app_session.headers.update({'Authorization': f'token {token}'})

def do_request(self, query=None, method=None, json_payload=None, url=None, use_github_auth=False):
def do_request(self, query=None, method=None, json_payload=None, url=None,
use_github_auth=False):
"""
a single wrapper to make any type of request:
Expand All @@ -136,21 +137,25 @@ def do_request(self, query=None, method=None, json_payload=None, url=None, use_g
if query:
self.logger.debug(f'query = {query}')
if use_github_auth and self.github_app_session:
response = self.github_app_session.post(url=self.API_ENDPOINT, json={'query': query})
response = self.github_app_session.post(url=self.API_ENDPOINT,
json={'query': query})
else:
response = self.session.post(url=self.API_ENDPOINT, json={'query': query})
if response.status_code == 401 and self.github_app_session:
self.update_github_app_token()
response = self.github_app_session.post(url=self.API_ENDPOINT, json={'query': query})
response = self.github_app_session.post(url=self.API_ENDPOINT,
json={'query': query})
elif method and url:
self.logger.debug(f'{method} {url}')
if use_github_auth and self.github_app_session:
response = self.github_app_session.request(method=method, url=url, json=json_payload)
response = self.github_app_session.request(method=method, url=url,
json=json_payload)
else:
response = self.session.request(method=method, url=url, json=json_payload)
if response.status_code == 401 and self.github_app_session:
self.update_github_app_token()
response = self.github_app_session.request(method=method, url=url, json=json_payload)
response = self.github_app_session.request(method=method, url=url,
json=json_payload)
if not response.ok:
self.logger.error(f"error message: {response.content}")
else:
Expand Down Expand Up @@ -247,30 +252,6 @@ def walk_through_prs(self, start='', direction='after', which="last", closed=Tru
self.detect_api_errors(response)
return response['data']['repository']['pullRequests']['edges']

def walk_through_open_issues(self, start='', direction='after', which="last"):
"""
Searches open issues for a release trigger
:return: edges from API query response
"""
while True:
query = (f"issues(states: OPEN {which}: 5 " +
(f'{direction}: "{start}"' if start else '') +
'''){
edges {
cursor
node {
id
number
title
authorAssociation
}
}
}''')
response = self.query_repository(query).json()
self.detect_api_errors(response)
return response['data']['repository']['issues']['edges']

def make_new_release(self, new_release):
"""
Makes new release to Github.
Expand All @@ -288,7 +269,8 @@ def make_new_release(self, new_release):
url = (f"{self.API3_ENDPOINT}repos/{self.conf.repository_owner}/"
f"{self.conf.repository_name}/releases")
self.logger.debug(f"About to release {new_release.version} on Github")
response = self.do_request(method="POST", url=url, json_payload=payload, use_github_auth=True)
response = self.do_request(method="POST", url=url, json_payload=payload,
use_github_auth=True)
if response.status_code != 201:
msg = f"Failed to create new release on github:\n{response.text}"
raise ReleaseException(msg)
Expand Down Expand Up @@ -320,7 +302,8 @@ def update_changelog(self, new_version):

url = (f"{self.API3_ENDPOINT}repos/{self.conf.repository_owner}/"
f"{self.conf.repository_name}/releases/{latest_release['id']}")
response = self.do_request(method="POST", url=url, json_payload={'body': changelog}, use_github_auth=True)
response = self.do_request(method="POST", url=url, json_payload={'body': changelog},
use_github_auth=True)
if response.status_code != 200:
self.logger.error((f"Something went wrong during changelog "
f"update for {new_version}:\n{response.text}"))
Expand Down Expand Up @@ -379,7 +362,8 @@ def make_pr(self, branch, version, log, changed_version_files, base='master', la
url = (f"{self.API3_ENDPOINT}repos/{self.conf.repository_owner}/"
f"{self.conf.repository_name}/pulls")
self.logger.debug(f'Attempting a PR for {branch} branch')
response = self.do_request(method="POST", url=url, json_payload=payload, use_github_auth=True)
response = self.do_request(method="POST", url=url, json_payload=payload,
use_github_auth=True)
if response.status_code == 201:
parsed = response.json()
self.logger.info(f"Created PR: {parsed['html_url']}")
Expand Down Expand Up @@ -482,23 +466,6 @@ def get_user_contact(self):
email = '[email protected]'
return name, email

def close_issue(self, number):
"""
Close an github issue
:param number: number of the issue in repository
:return: True on success, False on fail
"""
payload = {'state': 'closed'}
url = (f"{self.API3_ENDPOINT}repos/{self.conf.repository_owner}/"
f"{self.conf.repository_name}/issues/{number}")
self.logger.debug(f'Attempting to close issue #{number}')
response = self.do_request(method='PATCH', url=url, json_payload=payload, use_github_auth=True)
if response.status_code == 200:
self.logger.debug(f'Closed issue #{number}')
return True
self.logger.error(f'Failed to close issue #{number}')
return False

def put_labels_on_issue(self, number, labels):
"""
Put labels on Github issue or PR
Expand All @@ -524,21 +491,14 @@ def put_labels_on_issue(self, number, labels):
def get_file(self, name):
"""
Fetches a specific file via Github API
@:param: str, name of the file
:return: file content or None in case of error
"""
url = (f"{self.API3_ENDPOINT}repos/{self.conf.repository_owner}/"
f"{self.conf.repository_name}/contents/{name}")
self.logger.debug(f'Fetching {name}')
response = self.do_request(url=url, method='GET')
if response.status_code != 200:
self.logger.error(f'Failed to fetch {name}')
return None

parsed = response.json()
download_url = parsed['download_url']
response = requests.get(url=download_url)
if response.status_code != 200:
try:
file = self.project.get_file_content(path=name)
except FileNotFoundError:
self.logger.error(f'Failed to fetch {name}')
return None

return response.text
return file
67 changes: 40 additions & 27 deletions release_bot/releasebot.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from flask import Flask
from semantic_version import Version
from ogr import GithubService, PagureService
from ogr.abstract import IssueStatus

from release_bot.cli import CLI
from release_bot.configuration import configuration
Expand Down Expand Up @@ -99,45 +100,56 @@ def which_service(self):
return "Pagure"
return None

def which_username(self):
"""
Returns Github/Pagure username based on current project service
:return: str
"""
if self.which_service() == "Github":
return self.conf.github_username
elif self.which_service() == "Pagure":
return self.conf.pagure_username
return None

def find_open_release_issues(self):
"""
Looks for opened release issues on github
:return: True on found, False if not found
"""
cursor = ''
release_issues = {}
latest_version = Version(self.github.latest_release())
while True:
edges = self.github.walk_through_open_issues(start=cursor, direction='before')
if not edges:
self.logger.debug(f'No more open issues found')
break
else:
for edge in reversed(edges):
cursor = edge['cursor']
title = edge['node']['title'].lower().strip()
match, version = process_version_from_title(title, latest_version)
if match:
if edge['node']['authorAssociation'] in ['MEMBER', 'OWNER',
'COLLABORATOR']:
release_issues[version] = edge['node']
self.logger.info(f'Found new release issue with version: {version}')
else:
self.logger.warning(
f"Author association {edge['node']['authorAssociation']!r} "
f"not in ['MEMBER', 'OWNER', 'COLLABORATOR']")
opened_issues = self.project.get_issue_list(IssueStatus.open)
if not opened_issues:
self.logger.debug(f'No more open issues found')
else:
for issue in opened_issues:
match, version = process_version_from_title(issue.title, latest_version)
if match:
if self.project.can_close_issue(self.which_username(), issue):
release_issues[version] = issue
self.logger.info(f'Found new release issue with version: {version}')
else:
self.logger.warning(f"User {self.which_username()} "
f"has no permission to modify issue")

if len(release_issues) > 1:
msg = f'Multiple release issues are open {release_issues}, please reduce them to one'
self.logger.error(msg)
return False
if len(release_issues) == 1:
for version, node in release_issues.items():
if self.which_service() == "Github":
labels = self.new_release.labels
else:
# Putting labels on Pagure issues is not implemented yet inside ogr-lib
labels = None

for version, issue in release_issues.items():
self.new_pr.update_new_pr_details(
version=version,
issue_id=node['id'],
issue_number=node['number'],
labels=self.new_release.labels
issue_id=None,
issue_number=issue.id,
labels=labels
)
return True
else:
Expand Down Expand Up @@ -199,7 +211,8 @@ def pr_handler(success):
self.project.issue_comment(self.new_pr.issue_number, msg)
self.github.comment = comment_backup
if success:
self.github.close_issue(self.new_pr.issue_number)
self.project.issue_close(self.new_pr.issue_number)
self.logger.debug(f'Closed issue #{self.new_pr.issue_number}')

latest_gh_str = self.github.latest_release()
self.new_pr.previous_version = latest_gh_str
Expand Down Expand Up @@ -312,8 +325,8 @@ def run(self):
try:
if self.new_release.trigger_on_issue and self.find_open_release_issues():
if self.new_release.labels is not None:
self.github.put_labels_on_issue(self.new_pr.issue_number,
self.new_release.labels)
self.project.add_issue_labels(self.new_pr.issue_number,
self.new_release.labels)
self.make_release_pull_request()
except ReleaseException as exc:
self.logger.error(exc)
Expand Down
9 changes: 9 additions & 0 deletions tests/test_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,15 @@ def test_git_service(self):
git_service = self.release_bot.git_service
assert git_service == "Github"

def test_which_service(self):
git_service = self.release_bot.which_service()
assert git_service == "Github"

def test_which_username(self):
git_username = self.release_bot.which_username()
assert git_username == self.github_user
assert git_username == self.release_bot.conf.github_username

def test_find_open_rls_issue(self, open_issue):
"""Tests if bot can find opened release issue"""
assert self.release_bot.find_open_release_issues()
Expand Down
6 changes: 2 additions & 4 deletions tests/test_github.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ def setup_method(self):
# set conf
configuration.repository_name = self.g_utils.repo
configuration.github_username = self.g_utils.github_user
configuration.clone_url = f"https://github.com/{self.g_utils.github_user}/{self.g_utils.repo}.git"
configuration.refresh_interval = 1
configuration.project = configuration.get_project()

repo_url = f"https://github.com/{self.g_utils.github_user}/{self.g_utils.repo}"
git = Git(repo_url, configuration)
Expand Down Expand Up @@ -80,10 +82,6 @@ def test_get_file(self):
"""Tests fetching release-conf from Github"""
assert self.github.get_file("release-conf.yaml") == RELEASE_CONF

def test_close_issue(self, open_issue):
"""Tests closing issue"""
assert self.github.close_issue(open_issue)

def test_latest_rls_not_existing(self):
"""Tests version number when there is no latest release"""
assert self.github.latest_release() == '0.0.0'
Expand Down

0 comments on commit c495449

Please sign in to comment.