Skip to content

Commit

Permalink
Merge pull request #3 from tarantool/deployment-to-docker
Browse files Browse the repository at this point in the history
Deployment to docker
  • Loading branch information
Gerold103 authored Oct 16, 2018
2 parents 460154e + b6173a1 commit 1211606
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 132 deletions.
9 changes: 7 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: gunicorn --bind 0.0.0.0:$PORT tarantoolbot:app
21 changes: 13 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<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
Expand Down
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
bottle==0.12.13
requests==2.19.1
gunicorn==19.9.0
270 changes: 148 additions & 122 deletions tarantoolbot.py
Original file line number Diff line number Diff line change
@@ -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 = []
Expand All @@ -22,17 +25,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 = '<tr>'
result += '<td>{}</td>'.format(time)
result += '<td>{}</td>'.format(author)
result += '<td>{}</td>'.format(action)
result += '<td>{}</td>'.format(body)
result += '</tr>'
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 = '<tr>'
result += '<td>{}</td>'.format(time)
result += '<td>{}</td>'.format(author)
result += '<td>{}</td>'.format(action)
result += '<td>{}</td>'.format(body)
result += '</tr>'
last_events.append(result)
if len(last_events) > LAST_EVENTS_SIZE:
last_events.pop(0)

##
# Bot to track documentation update requests
Expand All @@ -53,42 +58,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.
Expand All @@ -97,14 +108,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.
Expand All @@ -116,26 +129,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
Expand All @@ -148,18 +163,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
Expand All @@ -168,56 +185,65 @@ 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 ('<h1>{}</h1><table border="1" cellspacing="2" ' +\
'cellpadding="2">{}</table>').format('TarantoolBot Journal',
' '.join(last_events))
return ('<h1>{}</h1><table border="1" cellspacing="2" ' +
'cellpadding="2">{}</table>').format('TarantoolBot Journal',
' '.join(last_events))


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

0 comments on commit 1211606

Please sign in to comment.