Commit 2907202c by Will Daly

Added script to check whether PR repo owner is authorized

Added Python script to post GitHub status
parent 1888fe31
......@@ -6,6 +6,9 @@
- name: jenkins | Install virtualenv wrapper
pip: name=virtualenvwrapper state=present
- name: jenkins | Install requests
pip: name=requests state=present
# Install bash scripts
- name: jenkins | Install Python packages scripts
copy: src="${item}" dest="/usr/local/bin/${item}"
......@@ -16,6 +19,20 @@
- download_python_pkgs.sh
- install_python_pkgs.sh
# Install scripts requiring a GitHub OAuth token
- fail: GitHub OAuth token not defined
when: github_oauth_token is not defined
- name: jenkins | Install Python GitHub PR auth script
template: src="github_pr_auth.py.j2" dest="/usr/local/bin/github_pr_auth.py"
owner=root group=root
mode=755
- name: jenkins | Install Python GitHub post status script
template: src="github_post_status.py.j2" dest="/usr/local/bin/github_post_status.py"
owner=root group=root
mode=755
# Install upstart script to download Python packages from S3
- name: jenkins | Install Python packages upstart script
template: src="python_pkgs.conf.j2" dest="/etc/init/python_pkgs.conf"
#!/usr/bin/env python
"""
Update the status of a GitHub commit.
"""
import sys
import requests
import json
from textwrap import dedent
# The Ansible script will fill in the GitHub OAuth token.
# That way, we can give the jenkins user on the worker
# execute-only access to this script, ensuring that
# the jenkins user cannot retrieve the token.
GITHUB_OAUTH_TOKEN = "{{ github_oauth_token }}"
USAGE = "Usage: {0} ORG REPO SHA STATUS TARGET_URL DESCRIPTION"
VALID_STATUS_LIST = ['pending', 'success', 'error', 'failure']
def parse_args(arg_list):
"""
Parse the list of arguments, returning a dict.
Prints an error message and exits if the arguments are invalid.
"""
if len(arg_list) != 7:
print USAGE.format(arg_list[0])
exit(1)
# Check that the build status is valid
status = arg_list[4]
if not status in VALID_STATUS_LIST:
print "Invalid status: must be one of {0}".format(", ".join(VALID_STATUS_LIST))
exit(1)
return {
'org': arg_list[1],
'repo': arg_list[2],
'sha': arg_list[3],
'status': arg_list[4],
'target_url': arg_list[5],
'description': arg_list[6]
}
def post_status(org, repo, sha, status, target_url, description):
"""
Post a new status to GitHub.
See http://developer.github.com/v3/repos/statuses/ for details.
Prints an error message and exits if unsuccessful.
"""
url = "https://api.github.com/repos/{0}/{1}/statuses/{2}?access_token={3}".format(
org, repo, sha, GITHUB_OAUTH_TOKEN
)
params = {
'state': status,
'target_url': target_url,
'description': description
}
response = requests.post(url, data=json.dumps(params))
if response.status_code != 201:
print dedent("""
Could not post status:
HTTP response code is {0}
Content: {1}
""").format(response.status_code, response.text).strip()
exit(1)
def main():
"""
Post the status to GitHub.
"""
if not GITHUB_OAUTH_TOKEN:
print "No GitHub Oauth token configured."
exit(1)
arg_dict = parse_args(sys.argv)
post_status(
arg_dict['org'], arg_dict['repo'],
arg_dict['sha'], arg_dict['status'],
arg_dict['target_url'], arg_dict['description']
)
if __name__ == "__main__":
main()
#!/usr/bin/env python
"""
Determine whether we allow a GitHub PR to be
built automatically. Checks a whitelist
of repo owners and compares to the HEAD
repo of the pull request.
Uses an environment variable `GITHUB_OWNER_WHITELIST`
to check whether the owner of the PR repo is whitelisted.
This is a comma-separated list of organizations and
users. For example, a bash script might define:
export GITHUB_OWNER_WHITELIST="edx,a_user,another_user"
to allow PRs from repos owned by "edx", "a_usr", and "another_user"
"""
import sys
import os
import requests
from textwrap import dedent
# The Ansible script will fill in the GitHub OAuth token.
# That way, we can give the jenkins user on the worker
# execute-only access to this script, ensuring that
# the jenkins user cannot retrieve the token.
GITHUB_OAUTH_TOKEN = "{{ github_oauth_token }}"
USAGE = "Usage: {0} ORG REPO PULL_REQUEST_NUM"
def parse_args(arg_list):
"""
Parse the list of arguments, returning a dict of the form
{
'org': GITHUB_ORG,
'repo': GITHUB_REPO,
'pr_num': GITHUB_PR_NUM
}
Prints an error message and exits if the arguments are invalid.
"""
if len(arg_list) != 4:
print USAGE.format(arg_list[0])
exit(1)
# Retrieve the PR number and check that it's an integer
try:
pr_num = int(arg_list[3])
except TypeError:
print "'{0}' is not a number".format(arg_list[3])
return {
'org': arg_list[1],
'repo': arg_list[2],
'pr_num': pr_num
}
def pr_repo_owner(org, repo, pr_num):
"""
Return the name of the owner of the repo from the
HEAD of the PR.
"""
# Query GitHub for information about the pull request
url = "https://api.github.com/repos/{0}/{1}/pulls/{2}?access_token={3}".format(
org, repo, pr_num, GITHUB_OAUTH_TOKEN
)
response = requests.get(url)
if response.status_code != 200:
print dedent("""
Could not retrieve info for pull request #{0}.
HTTP status code: {1}
""".format(pr_num, response.status_code)).strip()
exit(1)
# Parse the response as json
try:
pr_data = response.json()
except TypeError:
print "Could not parse info for pull request #{0}".format(pr_num)
exit(1)
# Retrieve the owner of the repo
try:
return pr_data['head']['repo']['owner']['login']
except KeyError:
print "Could not get repo owner from PR info"
exit(1)
def main():
"""
Exits with code 0 (success) if the PR is from a whitelisted
repo; otherwise, exits with status 1 (failure).
"""
if not GITHUB_OAUTH_TOKEN:
print "No GitHub Oauth token configured."
exit(1)
arg_dict = parse_args(sys.argv)
owner = pr_repo_owner(arg_dict['org'], arg_dict['repo'], arg_dict['pr_num'])
# Check that the owner is whitelisted
whitelist_owners = os.environ.get('GITHUB_OWNER_WHITELIST', '').split(',')
if not owner in whitelist_owners:
print dedent("""
Owner '{0}' is not in the whitelist.
You can update the whitelist by setting the environment variable
`GITHUB_OWNER_WHITELIST` to a comma-separated list of organizations
and users.
""".format(owner)).strip()
exit(1)
else:
print "Owner '{0}' is authorized".format(owner)
exit(0)
if __name__ == "__main__":
main()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment