From 576de11c5ca93a1ad323459d5cb667cdb3c65a08 Mon Sep 17 00:00:00 2001 From: Konstantin Nazarov Date: Tue, 16 Oct 2018 14:35:40 +0300 Subject: [PATCH 1/2] Run autopep8 to make code conform to PEP8 --- tarantoolbot.py | 251 ++++++++++++++++++++++++++---------------------- 1 file changed, 135 insertions(+), 116 deletions(-) diff --git a/tarantoolbot.py b/tarantoolbot.py index 8d1bac7..2f9207d 100644 --- a/tarantoolbot.py +++ b/tarantoolbot.py @@ -22,17 +22,19 @@ # @param author GitHub login of the author. # @param action What to log. # @param body Message body. + + def create_event(author, action, body): - time = datetime.datetime.now().strftime('%b %d %H:%M:%S') - result = '' - result += '{}'.format(time) - result += '{}'.format(author) - result += '{}'.format(action) - result += '{}'.format(body) - result += '' - last_events.append(result) - if len(last_events) > LAST_EVENTS_SIZE: - last_events.pop(0) + time = datetime.datetime.now().strftime('%b %d %H:%M:%S') + result = '' + result += '{}'.format(time) + result += '{}'.format(author) + result += '{}'.format(action) + result += '{}'.format(body) + result += '' + last_events.append(result) + if len(last_events) > LAST_EVENTS_SIZE: + last_events.pop(0) ## # Bot to track documentation update requests @@ -53,42 +55,48 @@ def create_event(author, action, body): # @retval not None - @A body is a well-formatted # request. Returned value is a dictionary # with 'title' and 'description'. + + def parse_comment(body): - if not body.startswith(bot_name): - return None, None - offset = len(bot_name) - for dr in doc_requests: - if body.startswith(dr, offset): - offset += len(dr) - break - else: - return None, 'Invalid request type.' - if not body.startswith(title_header, offset): - return None, 'Title not found.' - offset += len(title_header) - pos = body.find('\n', offset) - if pos == -1: - pos = len(body) - return {'title': body[offset:pos], - 'description': body[pos:]}, None + if not body.startswith(bot_name): + return None, None + offset = len(bot_name) + for dr in doc_requests: + if body.startswith(dr, offset): + offset += len(dr) + break + else: + return None, 'Invalid request type.' + if not body.startswith(title_header, offset): + return None, 'Title not found.' + offset += len(title_header) + pos = body.find('\n', offset) + if pos == -1: + pos = len(body) + return {'title': body[offset:pos], + 'description': body[pos:]}, None ## # Send a comment to an issue. Sent comment is either # parser error message, or notification about accepting # a request. + + def send_comment(body, issue_api, to): - create_event(to, 'send_comment', body) - body = {'body': '@{}: {}'.format(to, body)} - url = '{}/comments?access_token={}'.format(issue_api, token) - r = requests.post(url, json=body) - print('Sent comment: {}'.format(r.status_code)) + create_event(to, 'send_comment', body) + body = {'body': '@{}: {}'.format(to, body)} + url = '{}/comments?access_token={}'.format(issue_api, token) + r = requests.post(url, json=body) + print('Sent comment: {}'.format(r.status_code)) ## # Get all the comments of an issue. + + def get_comments(issue_api): - body = {'since': '1970-01-01T00:00:00Z'} - url = '{}/comments?access_token={}'.format(issue_api, token) - r = requests.get(url, json=body) - return r.json() + body = {'since': '1970-01-01T00:00:00Z'} + url = '{}/comments?access_token={}'.format(issue_api, token) + r = requests.get(url, json=body) + return r.json() ## # Open a new issue in a documentation repository. @@ -97,14 +105,16 @@ def get_comments(issue_api): # @param src_url Link to a webpage with the original issue or # commit. # @param author Github login of the request author. + + def create_issue(title, description, src_url, author): - create_event(author, 'create_issue', title) - description = '{}\nRequested by @{} in {}.'.format(description, author, - src_url) - body = {'title': title, 'body': description} - url = '{}/issues?access_token={}'.format(doc_repo_url, token) - r = requests.post(url, json=body) - print('Created issue: {}'.format(r.status_code)) + create_event(author, 'create_issue', title) + description = '{}\nRequested by @{} in {}.'.format(description, author, + src_url) + body = {'title': title, 'body': description} + url = '{}/issues?access_token={}'.format(doc_repo_url, token) + r = requests.post(url, json=body) + print('Created issue: {}'.format(r.status_code)) ## # Check that a new or edited comment for an issue is the request. @@ -116,26 +126,28 @@ def create_issue(title, description, src_url, author): # response is wrote. # # @retval Response to a GitHub hook. + + def process_issue_comment(body, issue_state, issue_api): - action = body['action'] - if (action != 'created' and action != 'edited') or \ - issue_state != 'open': - return 'Not needed.' - comment = body['comment'] - author = comment['user']['login'] - comment, error = parse_comment(comment['body']) - if error: - print('Error during request processing: {}'.format(error)) - send_comment(error, issue_api, author) - elif comment: - print('Request is processed ok') - if action == 'edited': - send_comment('Accept edited.', issue_api, author) - else: - send_comment('Accept.', issue_api, author) - else: - print('Ignore non-request comments') - return 'Doc request is processed.' + action = body['action'] + if (action != 'created' and action != 'edited') or \ + issue_state != 'open': + return 'Not needed.' + comment = body['comment'] + author = comment['user']['login'] + comment, error = parse_comment(comment['body']) + if error: + print('Error during request processing: {}'.format(error)) + send_comment(error, issue_api, author) + elif comment: + print('Request is processed ok') + if action == 'edited': + send_comment('Accept edited.', issue_api, author) + else: + send_comment('Accept.', issue_api, author) + else: + print('Ignore non-request comments') + return 'Doc request is processed.' ## # Check that a just closed issue contains contains @@ -148,18 +160,20 @@ def process_issue_comment(body, issue_state, issue_api): # @param issue_url Public URL of the original issue. # # @retval Response to a GitHub hook. + + def process_issue_state_change(body, issue_state, issue_api, issue_url): - action = body['action'] - if action != 'closed': - return 'Not needed.' - comments = get_comments(issue_api) - comment = None - for c in comments: - comment, error = parse_comment(c['body']) - if comment: - create_issue(comment["title"], comment["description"], - issue_url, c['user']['login']) - return 'Issue is processed.' + action = body['action'] + if action != 'closed': + return 'Not needed.' + comments = get_comments(issue_api) + comment = None + for c in comments: + comment, error = parse_comment(c['body']) + if comment: + create_issue(comment["title"], comment["description"], + issue_url, c['user']['login']) + return 'Issue is processed.' ## # Process a commit event, triggered on any push. If in the commit @@ -168,56 +182,61 @@ def process_issue_state_change(body, issue_state, issue_api, issue_url): # @param c Commit object. # @param is_master_push True if the commit is pushed into the # master branch. + + def process_commit(c, is_master_push): - body = c['message'] - request_pos = body.find(bot_name) - if request_pos == -1: - return - request = body[request_pos:] - author = c['author']['username'] - comment, error = parse_comment(request) - if error: - print('Error during request processing: {}'.format(error)) - create_event(author, 'process_commit', error) - else: - create_event(author, 'process_commit', 'Accept') - if is_master_push: - create_issue(comment['title'], comment['description'], - c['url'], author) + body = c['message'] + request_pos = body.find(bot_name) + if request_pos == -1: + return + request = body[request_pos:] + author = c['author']['username'] + comment, error = parse_comment(request) + if error: + print('Error during request processing: {}'.format(error)) + create_event(author, 'process_commit', error) + else: + create_event(author, 'process_commit', 'Accept') + if is_master_push: + create_issue(comment['title'], comment['description'], + c['url'], author) + @post('/') def index_post(): - r = request.json - t = request.get_header('X-GitHub-Event') - if 'issue' in r: - issue = r['issue'] - if not 'state' in issue: - return 'Event is not needed.' - issue_state = issue['state'] - issue_api = issue['url'] - issue_url = issue['html_url'] - - if t == 'issue_comment': - return process_issue_comment(r, issue_state, issue_api) - elif t == 'issues': - return process_issue_state_change(r, issue_state, - issue_api, issue_url) - else: - return 'Event "{}" is not needed.'.format(t) - elif t == 'push': - repo = r['repository'] - branch = r['ref'].split('/')[-1] - is_master_push = repo['master_branch'] == branch - for c in r['commits']: - process_commit(c, is_master_push) - else: - return 'Event is not needed.' + r = request.json + t = request.get_header('X-GitHub-Event') + if 'issue' in r: + issue = r['issue'] + if not 'state' in issue: + return 'Event is not needed.' + issue_state = issue['state'] + issue_api = issue['url'] + issue_url = issue['html_url'] + + if t == 'issue_comment': + return process_issue_comment(r, issue_state, issue_api) + elif t == 'issues': + return process_issue_state_change(r, issue_state, + issue_api, issue_url) + else: + return 'Event "{}" is not needed.'.format(t) + elif t == 'push': + repo = r['repository'] + branch = r['ref'].split('/')[-1] + is_master_push = repo['master_branch'] == branch + for c in r['commits']: + process_commit(c, is_master_push) + else: + return 'Event is not needed.' + @get('/') def index_get(): - return ('

{}

{}
').format('TarantoolBot Journal', - ' '.join(last_events)) + return ('

{}

{}
').format('TarantoolBot Journal', + ' '.join(last_events)) + print('Starting bot') run(host='0.0.0.0', port=8888) From b6173a1e6d893d60773d97e62f5de978c0accec5 Mon Sep 17 00:00:00 2001 From: Konstantin Nazarov Date: Tue, 16 Oct 2018 14:45:10 +0300 Subject: [PATCH 2/2] Allow to run with gunicorn in Docker and Heroku --- Dockerfile | 9 +++++++-- Procfile | 1 + README.md | 21 +++++++++++++-------- requirements.txt | 3 +++ tarantoolbot.py | 19 +++++++++++++------ 5 files changed, 37 insertions(+), 16 deletions(-) create mode 100644 Procfile create mode 100644 requirements.txt diff --git a/Dockerfile b/Dockerfile index e5204e1..5a0546d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,9 +4,14 @@ RUN mkdir -p /usr/src/bot WORKDIR /usr/src/bot RUN pip install --upgrade pip -RUN pip install bottle requests +COPY requirements.txt /usr/src/bot COPY tarantoolbot.py /usr/src/bot COPY write_credentials.sh /usr/src/bot -EXPOSE 8888 +RUN pip install -r requirements.txt + +ENV PORT=5000 +EXPOSE 5000 + +CMD gunicorn --bind 0.0.0.0:$PORT tarantoolbot:app diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..9321b17 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: gunicorn --bind 0.0.0.0:$PORT tarantoolbot:app \ No newline at end of file diff --git a/README.md b/README.md index d674fbc..bfc4bde 100644 --- a/README.md +++ b/README.md @@ -33,14 +33,19 @@ into trunk, changing some external behaviour. ## Deployment -The TarantoolBot deployment is quite simple. -1. Define environment variable GITHUB_TOKEN with the token generated by GitHub - for the bot account; -2. Run `write_credentials.sh`; -3. Install `bottle` and `requests` via `pip`; -4. Run the bot with `python tarantoolbot.py`. - -To check that it works try to get `localhost:8888` - it will print the +Build Docker container with: + +```sh +docker build -t docbot . +``` + +Then run it like this: + +```sh +docker run -d -p5000:5000 -e GITHUB_TOKEN= --name docbot docbot +``` + +To check that it works try to get `localhost:5000` - it will print the **TarantoolBot Journal** message, if all is ok. After this set a GitHub hook in the repository, that you want the bot tracks. The hook must send notifications about new comments in all issues, about closed issues, log all events onto its diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0d98724 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +bottle==0.12.13 +requests==2.19.1 +gunicorn==19.9.0 diff --git a/tarantoolbot.py b/tarantoolbot.py index 2f9207d..557f86b 100644 --- a/tarantoolbot.py +++ b/tarantoolbot.py @@ -1,17 +1,20 @@ -from bottle import run, post, get, request +from bottle import run, post, get, request, default_app import requests import json import datetime +import os doc_requests = [' document\r\n', ' document\n'] bot_name = '@TarantoolBot' title_header = 'Title:' doc_repo_url = 'https://api.github.com/repos/tarantool/doc' -f = open('credentials.json') -credentials = json.loads(f.read()) -f.close() -token = credentials['GitHub']['token'] +if os.getenv('GITHUB_TOKEN'): + token = os.getenv('GITHUB_TOKEN') +else: + with open('credentials.json') as f: + credentials = json.loads(f.read()) + token = credentials['GitHub']['token'] LAST_EVENTS_SIZE = 30 last_events = [] @@ -239,4 +242,8 @@ def index_get(): print('Starting bot') -run(host='0.0.0.0', port=8888) +if __name__ == "__main__": + run(host='0.0.0.0', port=5000) + +# allow to run under gunicorn +app = default_app()