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()