webhook-app/github_helper.py (91 lines of code) (raw):
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Helpers for interacting with the GitHub API and hook events."""
import json
import os
import github3
def github_user():
"""Returns the bot's username."""
return os.environ['GITHUB_USER']
def get_client():
"""Returns an authenticated github3 client."""
gh = github3.login(
github_user(), os.environ['GITHUB_ACCESS_TOKEN'])
return gh
def get_repository(gh, data):
"""Gets the repository from hook event data."""
return gh.repository(
data['repository']['owner']['login'], data['repository']['name'])
def is_pull_request(data):
"""Checks if the hook event data is for a pull request."""
return data.get('issue', {}).get('pull_request') is not None
def get_pull_request(gh, data):
"""Gets the pull request from hook event data."""
return gh.pull_request(
data['repository']['owner']['login'], data['repository']['name'],
data['issue']['number'])
def accept_all_invitations(gh):
"""Accepts all invitations and returns a list of repositories."""
# Required to access the invitations API.
headers = {'Accept': 'application/vnd.github.swamp-thing-preview+json'}
invitations = gh.session.get(
'https://api.github.com/user/repository_invitations',
headers=headers).json()
for invitation in invitations:
gh.session.patch(invitation['url'], headers=headers)
return [invitation['repository'] for invitation in invitations]
def get_pr_requested_reviewers(pr):
"""Gets a list of all requested reviewers on a PR."""
url = (
'https://api.github.com/repos/{}/{}/pulls/{}'
'/requested_reviewers'.format(
pr.repository[0], pr.repository[1], pr.number))
reviewers = pr.session.get(url).json()
return reviewers.get('users', [])
def get_pr_reviews(pr):
"""Gets a list of all submitted reviews on a PR. Does not list requested
reviews."""
# Required to access the PR review API.
headers = {'Accept': 'application/vnd.github.black-cat-preview+json'}
reviews = pr.session.get(
'https://api.github.com/repos/{}/{}/pulls/{}/reviews'.format(
pr.repository[0], pr.repository[1], pr.number),
headers=headers).json()
return reviews
def get_pr_required_statuses(pr):
"""Gets a list off all of the required statuses for a PR to be merged."""
statuses = pr.session.get(
'https://api.github.com/repos/{}/{}/branches/{}/protection/'
'required_status_checks/contexts'.format(
pr.repository[0], pr.repository[1], pr.base.ref)).json()
return statuses
def get_pr_statuses(pr):
"""Gets a list of currently reported statuses for the commit."""
statuses = pr.session.get(
'https://api.github.com/repos/{}/{}/commits/{}/'
'statuses'.format(
pr.repository[0], pr.repository[1], pr.head.sha)).json()
return [status['context'] for status in statuses]
def has_required_statuses(pr):
"""Returns True if the PR has all the protected statuses present."""
required = get_pr_required_statuses(pr)
if not len(required):
return True
listed = get_pr_statuses(pr)
return set(required).issubset(set(listed))
def is_pr_approved(pr):
"""True if the PR has been completely approved."""
review_requests = get_pr_requested_reviewers(pr)
if not len(review_requests):
return True
reviews = get_pr_reviews(pr)
approved_users = [
review['user']['login'] for review in reviews
if review['state'] == 'APPROVED']
requested_users = [user['login'] for user in review_requests]
return set(approved_users) == set(requested_users)
def is_sha_green(repo, sha):
url = 'https://api.github.com/repos/{}/{}/commits/{}/status'.format(
repo.owner.login, repo.name, sha)
result = repo.session.get(url).json()
return result['state'] == 'success'
def get_permission(gh, owner, repo, user):
# Required to access the collaborators API.
headers = {'Accept': 'application/vnd.github.korra-preview'}
result = gh.session.get(
'https://api.github.com/repos/{}/{}/collaborators'
'/{}/permission'.format(owner, repo, user),
headers=headers).json()
return result['permission']
def squash_merge_pr(pr, sha):
data = {
'sha': sha,
'merge_method': 'squash'
}
response = pr.session.put(
'https://api.github.com/repos/{}/{}/pulls/{}/merge'.format(
pr.repository[0], pr.repository[1], pr.number),
json=data)
response.raise_for_status()
return response.json()