Skip to content
This repository was archived by the owner on Mar 14, 2023. It is now read-only.

Commit 9451dfc

Browse files
authored
Merge pull request #200 from rust-lang/modernize
Refactor highfive to be docker-friendly
2 parents 7720ac5 + 150e594 commit 9451dfc

15 files changed

+250
-132
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
config
1+
/.env
22
*.pyc
33
*~

.travis.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,12 @@ script:
1414
else
1515
pytest -m hermetic
1616
fi
17+
18+
# Publish the Docker image to the infra AWS registry
19+
before_deploy:
20+
- pip install --user awscli; export PATH=$PATH:$HOME/.local/bin
21+
deploy:
22+
provider: script
23+
script: sh ci/publish-docker.sh
24+
on:
25+
branch: master

Dockerfile

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,21 @@
11
FROM ubuntu:bionic
22

3-
RUN apt-get update && apt-get install -y apache2 \
4-
ca-certificates \
5-
python
3+
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
4+
ca-certificates \
5+
python \
6+
python-setuptools \
7+
python-wheel \
8+
python-pip
69

7-
# Set up Highfive
810
RUN mkdir /highfive
911
WORKDIR /highfive
1012

1113
COPY setup.py .
1214
COPY highfive/*.py highfive/
1315
COPY highfive/configs/ highfive/configs/
14-
RUN python setup.py install
15-
RUN touch highfive/config
16-
RUN chown -R www-data:www-data .
17-
18-
# Set up Apache
19-
WORKDIR /etc/apache2
20-
COPY deployment/highfive.conf conf-available/highfive.conf
21-
RUN a2enmod cgi
22-
RUN rm conf-enabled/serve-cgi-bin.conf
23-
RUN rm sites-enabled/*
24-
RUN ln -s ../conf-available/highfive.conf conf-enabled
25-
26-
RUN mkdir /var/log/highfive-tracebacks && chown www-data: /var/log/highfive-tracebacks
16+
RUN pip install .
2717

2818
EXPOSE 80
29-
CMD apachectl -D FOREGROUND
19+
ENV HIGHFIVE_PORT 80
20+
21+
CMD highfive

README.md

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -142,26 +142,31 @@ Local Development
142142
You can run Highfive on your machine and configure a repository to use
143143
your local instance. Here is one approach for running a local server:
144144

145-
- Use [serve.py](/serve.py) to run the Highfive service. From the
146-
repository root, do:
145+
- Create a [virtualenv](https://virtualenv.pypa.io/en/stable/) to isolate the
146+
Python environment from the rest of the system, and install highfive in it:
147147
```
148-
$ PYTHONPATH=$PYTHONPATH:$PWD python serve.py
148+
$ virtualenv -p python2 env
149+
$ env/bin/pip install -e .
149150
```
150-
Now you have Highfive listening on port 8000 of your machine.
151-
- Your Highfive instance will need to be reachable from outside of your machine. One way to do this is to use [ngrok](https://ngrok.com/) to get a temporary domain name that proxies to your Highfive instance. Additionally, you will be able to use ngrok's inspector to easily examine and replay the requests.
151+
- Run the highfive command to start a development server on port 8000:
152+
```
153+
$ env/bin/highfive
154+
```
155+
- Your Highfive instance will need to be reachable from outside of your
156+
machine. One way to do this is to use [ngrok](https://ngrok.com/) to get a
157+
temporary domain name that proxies to your Highfive instance. Additionally,
158+
you will be able to use ngrok's inspector to easily examine and replay the
159+
requests.
152160
- Set up the webhook by following the instructions in [Enabling a
153161
Repo](#enabling-a-repo), substituting your local Highfive IP address
154162
or domain name and port number (if necessary).
155163
- Obtain an OAuth token. In the account you are creating the token in,
156164
go to https://github.com/settings/tokens. Grant access to the repository scope.
157-
- Put the authorization information obtained in the previous step into
158-
a file named config in the top of the repository (i.e., the
159-
directory containing this file). Here's a template of what it should
160-
look like:
165+
- Put the authorization information obtained in the previous step into a file
166+
named `.env` in the top of the repository (i.e., the directory containing
167+
this file). Here is a template of what it should look like:
161168
```
162-
[github]
163-
user: OAUTH_TOKEN_USER
164-
token: OAUTH_TOKEN
169+
HIGHFIVE_GITHUB_TOKEN=your-token
165170
```
166171
_Do not check in this file or commit your OAuth token to a
167172
repository in any other way. It is a secret._
@@ -181,13 +186,17 @@ Docker
181186

182187
Alternatively, you can build a Docker image that runs Highfive.
183188

184-
$ docker build -t highfive .
189+
```
190+
$ docker build -t highfive .
191+
```
185192

186193
To run a container, you must mount a config file. Assuming you are
187194
launching a container from a directory containing a config file, you
188195
can do the following.
189196

190-
$ docker run -d --rm --name highfive -p 8080:80 -v $(pwd)/config:/highfive/highfive/config highfive
197+
```
198+
$ docker run -d --rm --name highfive -p 8000:80 -e HIGHFIVE_GITHUB_TOKEN=token -e HIGHFIVE_WEBHOOK_SECRET=secret highfive
199+
```
191200

192201
At this point, Highfive is accessible at http://localhost:8080.
193202

ci/publish-docker.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
IFS=$'\n\t'
4+
5+
ECR_IMAGE="890664054962.dkr.ecr.us-west-1.amazonaws.com/rust-highfive:latest"
6+
7+
$(aws ecr get-login --no-include-email --region us-west-1)
8+
9+
docker build -t rust-highfive .
10+
docker tag rust-highfive:latest "${ECR_IMAGE}"
11+
docker push "${ECR_IMAGE}"

deployment/highfive.conf

Lines changed: 0 additions & 7 deletions
This file was deleted.

highfive/app.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import hashlib
2+
import hmac
3+
import json
4+
import sys
5+
6+
from .config import Config, InvalidTokenException
7+
from .newpr import HighfiveHandler, UnsupportedRepoError
8+
from .payload import Payload
9+
10+
import click
11+
import dotenv
12+
import flask
13+
import waitress
14+
15+
16+
def create_app(config, webhook_secret=None):
17+
app = flask.Flask(__name__)
18+
19+
# The canonical URL is /webhook, but other URLs are accepted for backward
20+
# compatibility.
21+
@app.route("/webhook", methods=['POST'])
22+
@app.route("/newpr.py", methods=['POST'])
23+
@app.route("/highfive/newpr.py", methods=['POST'])
24+
def new_pr():
25+
raw_data = flask.request.get_data()
26+
27+
# Check the signature only if the secret is configured
28+
if 'payload' in flask.request.form and webhook_secret is not None:
29+
expected = hmac.new(str(webhook_secret), digestmod=hashlib.sha1)
30+
expected.update(raw_data)
31+
expected = expected.hexdigest()
32+
try:
33+
signature = str(flask.request.headers['X-Hub-Signature'])
34+
except KeyError:
35+
return 'Error: missing signature\n', 400
36+
if not hmac.compare_digest('sha1='+expected, signature):
37+
return 'Error: invalid signature\n', 403
38+
39+
try:
40+
payload = json.loads(flask.request.form['payload'])
41+
except (KeyError, ValueError), _:
42+
return 'Error: missing or invalid payload\n', 400
43+
try:
44+
handler = HighfiveHandler(Payload(payload), config)
45+
return handler.run(flask.request.headers['X-GitHub-Event'])
46+
except UnsupportedRepoError:
47+
return 'Error: this repository is not configured!\n', 400
48+
49+
@app.route('/')
50+
def index():
51+
return 'Welcome to highfive!\n'
52+
53+
return app
54+
55+
56+
@click.command()
57+
@click.option('--port', default=8000)
58+
@click.option('--github-token', required=True)
59+
@click.option("--webhook-secret")
60+
def cli(port, github_token, webhook_secret):
61+
try:
62+
config = Config(github_token)
63+
except InvalidTokenException:
64+
print 'error: invalid github token provided!'
65+
sys.exit(1)
66+
print 'Found a valid GitHub token for user @' + config.github_username
67+
68+
app = create_app(config, webhook_secret)
69+
waitress.serve(app, port=port)
70+
71+
72+
def main():
73+
dotenv.load_dotenv()
74+
cli(auto_envvar_prefix='HIGHFIVE')

highfive/config.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import requests
2+
3+
4+
class InvalidTokenException(Exception):
5+
pass
6+
7+
8+
class Config(object):
9+
def __init__(self, github_token):
10+
if not github_token:
11+
raise InvalidTokenException()
12+
self.github_token = github_token
13+
self.github_username = self.fetch_github_username()
14+
15+
def fetch_github_username(self):
16+
response = requests.get('https://api.github.com/user', headers={
17+
'Authorization': 'token ' + self.github_token
18+
})
19+
if response.status_code != 200:
20+
raise InvalidTokenException()
21+
return response.json()['login']

highfive/newpr.py

100755100644
Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from copy import deepcopy
77
import json
88
import random
9-
import sys
109
import ConfigParser
1110
from StringIO import StringIO
1211
import gzip
@@ -48,13 +47,11 @@ class UnsupportedRepoError(IOError):
4847
pass
4948

5049
class HighfiveHandler(object):
51-
def __init__(self, payload):
50+
def __init__(self, payload, config):
5251
self.payload = payload
5352

54-
self.config = ConfigParser.RawConfigParser()
55-
self.config.read('./config')
56-
self.integration_user = self.config.get('github', 'user')
57-
self.integration_token = self.config.get('github', 'token')
53+
self.integration_user = config.github_username
54+
self.integration_token = config.github_token
5855

5956
self.repo_config = self.load_repo_config()
6057

@@ -66,14 +63,17 @@ def load_repo_config(self):
6663
except IOError:
6764
raise UnsupportedRepoError
6865

69-
def run(self):
70-
if self.payload["action"] == "opened":
66+
def run(self, event):
67+
if event == "ping":
68+
return "Ping received! The webhook is configured correctly!\n"
69+
elif event == "pull_request" and self.payload["action"] == "opened":
7170
self.new_pr()
72-
elif self.payload["action"] == "created":
71+
return 'OK\n'
72+
elif event == "issue_comment" and self.payload["action"] == "created":
7373
self.new_comment()
74+
return 'OK\n'
7475
else:
75-
print self.payload["action"]
76-
sys.exit(0)
76+
return 'Unsupported webhook event.\n'
7777

7878
def _load_json_file(self, name):
7979
configs_dir = os.path.join(os.path.dirname(__file__), 'configs')
@@ -419,18 +419,3 @@ def new_comment(self):
419419
reviewer, owner, repo, issue, self.integration_user,
420420
author, None
421421
)
422-
423-
424-
if __name__ == "__main__":
425-
print "Content-Type: text/html;charset=utf-8"
426-
print
427-
428-
cgitb.enable(display=0, logdir="/var/log/highfive-tracebacks", format="txt")
429-
430-
post = cgi.FieldStorage()
431-
payload_raw = post.getfirst("payload",'')
432-
try:
433-
handler = HighfiveHandler(payload.Payload(json.loads(payload_raw)))
434-
handler.run()
435-
except UnsupportedRepoError:
436-
print "Unsupported repository"

highfive/tests/test_config.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import pytest
2+
import responses
3+
4+
from ..config import Config, InvalidTokenException
5+
6+
7+
@pytest.mark.unit
8+
@pytest.mark.hermetic
9+
class TestConfig(object):
10+
def test_empty_token(self):
11+
for token in ['', None]:
12+
with pytest.raises(InvalidTokenException):
13+
Config(token)
14+
15+
@responses.activate
16+
def test_valid_token(self):
17+
responses.add(
18+
responses.GET, 'https://api.github.com/user',
19+
json={'login': 'baz'},
20+
)
21+
22+
config = Config('foobar')
23+
assert config.github_token == 'foobar'
24+
assert config.github_username == 'baz'
25+
26+
@responses.activate
27+
def test_invalid_token(self):
28+
responses.add(responses.GET, 'https://api.github.com/user', status=403)
29+
30+
with pytest.raises(InvalidTokenException):
31+
Config('foobar')

0 commit comments

Comments
 (0)