Skip to content

Commit 9d60588

Browse files
authored
Docker configuration and other improvements 🐳 (#2)
* Fix old code references, add better logging and refactor code * Docker and Docker Compose configuration * Procfile configuration * Minor README improvements
1 parent dbbd3ae commit 9d60588

File tree

12 files changed

+215
-52
lines changed

12 files changed

+215
-52
lines changed

.dockerignore

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Files to ignore when building Docker image, basically the same than .gitignore
2+
3+
# Python compiled byte code
4+
*.pyc
5+
6+
# Staticfiles
7+
staticfiles/
8+
9+
# Log files
10+
*.log
11+
12+
# IDE files
13+
.idea
14+
*.iml
15+
*.ipr
16+
*.iws
17+
.settings
18+
.project
19+
.pydevproject
20+
.directory
21+
22+
# Script for different environments
23+
prod.sh
24+
dev.sh
25+
26+
# Static files collected
27+
/static
28+
29+
# Virtual environments
30+
venv*/
31+
.venv*/
32+
33+
/.env*
34+
35+
# Files from source that aren't needed in the image
36+
Dockerfile
37+
README*
38+
COPYING
39+
__*

.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ charset = utf-8
99
indent_style = space
1010
indent_size = 4
1111

12-
[*.html]
12+
[*.{html,yml}]
1313
indent_size = 2

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
PORT=8888
2+
DCOLEMAN_ENDPOINT=http://django-coleman:8000/api/v1
3+
DJANGO_COLEMAN_IP=10.0.0.4

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ dev.sh
2828
venv*/
2929
.venv*/
3030

31+
/.env

Dockerfile

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
FROM python:3.10-alpine
2+
LABEL maintainer="Mariano Ruiz <[email protected]>"
3+
4+
ENV PROCESS_TYPE="web" \
5+
PORT=8888
6+
7+
WORKDIR /usr/src/app
8+
9+
COPY requirements.txt /usr/src/app/
10+
11+
RUN apk add --no-cache --purge shadow \
12+
&& useradd -ms /bin/bash worker \
13+
&& chown -R worker:worker /usr/src/app \
14+
&& chown worker:worker requirements.txt \
15+
&& pip install --no-cache-dir -U pip \
16+
&& pip3 install --no-cache-dir \
17+
honcho \
18+
&& pip3 install --no-cache-dir -r requirements.txt
19+
20+
COPY --chown=worker ./ ./
21+
22+
ARG BUILD
23+
LABEL build=${BUILD}
24+
RUN echo "Build: $BUILD" > image_build \
25+
&& echo "UTC: $(date --utc +%FT%R)" >> image_build
26+
27+
RUN chown worker -R *
28+
29+
USER worker
30+
31+
CMD ["sh", "-c", "exec honcho start --no-prefix $PROCESS_TYPE"]

Procfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
web: ./app.py

README.rst

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,27 @@ Settings
7070
+---------------------------+---------+----------------------------------+--------------------------------------------------------------+
7171

7272

73+
Docker
74+
------
75+
76+
A reference `<Dockerfile>`_ is provided, that's is going to be published
77+
in Docker Hub *(in progress)*.
78+
79+
Also a `<docker-compose.yml>`_ is provided, you can build and run the
80+
the app with Docker Compose with::
81+
82+
$ docker-compose up
83+
84+
Although when using Docker Compose, it's recommended to use the compose
85+
file from the Django Coleman project instead of the local here, that
86+
allows to run all: Django Coleman, this Viewer app and Postgres at once.
87+
88+
7389
About
7490
-----
7591

7692
**Project**: https://github.com/mrsarm/tornado-dcoleman-mtasks-viewer
7793

78-
**Authors**: (2019-2021) Mariano Ruiz <[email protected]>
94+
**Authors**: (2019-2022) Mariano Ruiz <[email protected]>
7995

8096
**License**: AGPL-v3

app.py

Lines changed: 70 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/usr/bin/env python3
2+
23
import logging
34
import os
45
import datetime
@@ -13,23 +14,75 @@
1314
salt = os.getenv('DCOLEMAN_TASKS_VIEWER_HASH_SALT', '1two3')
1415
title = "Django Coleman - Task Viewer | {}".format
1516
mtasks_url = (endpoint + "/tasks/{}/").format
17+
port = int(os.getenv('PORT', 8888))
1618
master_t = os.getenv('DCOLEMAN_MASTER_TOKEN', 'porgs') # A master token to access to any order,
1719
# REPLACE in production or leave it blank to disable
1820

21+
logger = logging.getLogger('dcoleman-viewer')
22+
logger.setLevel(logging.INFO)
23+
handler = logging.StreamHandler()
24+
handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s [%(name)s] %(message)s'))
25+
logging.root.addHandler(handler)
26+
logging.root.setLevel(logging.INFO)
27+
28+
29+
def get_assigned_to(user):
30+
if not user:
31+
return ""
32+
full_name = " ".join(
33+
filter(None, (user.get("first_name"), user.get("last_name")))
34+
)
35+
return full_name if full_name else user["username"]
36+
37+
38+
def get_created_at(created_at):
39+
if not created_at:
40+
return ""
41+
return datetime.datetime.strptime(created_at, "%Y-%m-%dT%H:%M:%S.%fZ") \
42+
.strftime("%B %d, %Y")
43+
44+
45+
def get_deadline(deadline):
46+
if not deadline:
47+
return ""
48+
return datetime.datetime.strptime(deadline, "%Y-%m-%d") \
49+
.strftime("%B %d, %Y")
50+
51+
52+
def is_valid_token(order_number, token_url):
53+
"""
54+
Verifies whether the token is valid or not.
55+
It uses the same algorithm used by Django Coleman to
56+
generates the token using as input a salt code and
57+
the Order Number
58+
59+
See: ``mtasks.models.Task#get_tasks_viewer_url`` from the
60+
Django Coleman project
61+
"""
62+
if master_t and token_url == master_t: # Master Token is Magic !
63+
return True
64+
pk = int(order_number) # Removes the leading zeros
65+
token = "{}-{}".format(salt, pk)
66+
token = sha1(token.encode('utf-8')).hexdigest()
67+
return token == token_url
68+
69+
1970
class BaseHandler(RequestHandler):
71+
2072
def error(self, status, msg="Unexpected Error", exc_info=None):
2173
if exc_info:
22-
logging.error("Unexpected error %s", exc_info.__class__.__name__, exc_info=exc_info)
74+
logger.error("Unexpected error %s", exc_info.__class__.__name__, exc_info=exc_info)
2375
self.set_status(status)
2476
return self.render("error.html", title=title(msg), error=msg)
2577

2678

2779
class MainHandler(BaseHandler):
80+
2881
async def get(self, task_number):
2982
token = self.get_argument("t", None)
3083
if not token:
3184
return self.error(401, "Unauthorized Access")
32-
if not self.is_valid_token(task_number, token):
85+
if not is_valid_token(task_number, token):
3386
return self.error(401, "Invalid Authorization Code")
3487
http_client = AsyncHTTPClient()
3588
try:
@@ -43,9 +96,9 @@ async def get(self, task_number):
4396
return self.error(500, exc_info=e)
4497
order = json_decode(response.body)
4598
state = order['state']
46-
assigned_to = self.get_assigned_to(order.get('user'))
47-
created_at = self.get_created_at(order.get('created_at'))
48-
deadline = self.get_deadline(order.get('deadline'))
99+
assigned_to = get_assigned_to(order.get('user'))
100+
created_at = get_created_at(order.get('created_at'))
101+
deadline = get_deadline(order.get('deadline'))
49102
return self.render("index.html",
50103
title=title(f"Task #{task_number}"),
51104
order=order,
@@ -54,45 +107,9 @@ async def get(self, task_number):
54107
state=state,
55108
assigned_to=assigned_to)
56109

57-
def get_assigned_to(self, user):
58-
if not user:
59-
return ""
60-
full_name = " ".join(
61-
filter(None, (user.get("first_name"), user.get("last_name")))
62-
)
63-
return full_name if full_name else user["username"]
64-
65-
def get_created_at(self, created_at):
66-
if not created_at:
67-
return ""
68-
return datetime.datetime.strptime(created_at, "%Y-%m-%dT%H:%M:%S.%fZ") \
69-
.strftime("%B %d, %Y")
70-
71-
def get_deadline(self, deadline):
72-
if not deadline:
73-
return ""
74-
return datetime.datetime.strptime(deadline, "%Y-%m-%d") \
75-
.strftime("%B %d, %Y")
76-
77-
def is_valid_token(self, order_number, token_url):
78-
"""
79-
Verifies whether the token is valid or not.
80-
It uses the same algorithm used by Django Coleman to
81-
generates the token using as input a salt code and
82-
the Order Number
83-
84-
See: ``mtasks.models.Task#get_tasks_viewer_url`` from the
85-
Django Coleman project
86-
"""
87-
if master_t and token_url == master_t: # Master Token is Magic !
88-
return True
89-
pk = int(order_number) # Removes the leading zeros
90-
token = "{}-{}".format(salt, pk)
91-
token = sha1(token.encode('utf-8')).hexdigest()
92-
return token == token_url
93-
94110

95111
class NotFoundHandler(BaseHandler):
112+
96113
def prepare(self): # for all methods
97114
return self.error(404, "Resource Not Found")
98115

@@ -104,6 +121,15 @@ def make_app():
104121

105122

106123
if __name__ == "__main__":
124+
logger.info('Starting server at port %d', port)
107125
app = make_app()
108-
app.listen(8888)
109-
IOLoop.current().start()
126+
app.listen(port)
127+
try:
128+
loop = IOLoop.current()
129+
loop.start()
130+
except KeyboardInterrupt:
131+
pass
132+
finally:
133+
loop.stop()
134+
loop.close(True) # needed to close all open sockets
135+
logger.info("Server shut down, exiting...")

docker-build.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/usr/bin/env sh
2+
3+
# Get the tag
4+
if [ "$#" -gt 0 ] ; then
5+
export TAG="$1"
6+
#elif [ -n "$GITHUB_REF" ]; then
7+
# export TAG=${GITHUB_REF##*/}
8+
else
9+
export TAG="latest"
10+
fi
11+
12+
# Get the build
13+
if [ -n "$GITHUB_SHA" ] ; then
14+
GIT_HASH=${GITHUB_SHA}
15+
GIT_BRANCH=${GITHUB_REF#refs/heads/}
16+
else
17+
GIT_HASH=$(git rev-parse HEAD)
18+
GIT_BRANCH=$(git symbolic-ref --short HEAD)
19+
fi
20+
GIT_HASH_SHORT=$(git rev-parse --short "$GIT_HASH")
21+
export BUILD=${GIT_BRANCH}.${GIT_HASH_SHORT}
22+
23+
echo "Building mrsarm/django-coleman-mtasks-viewer:${TAG} with image_build $BUILD ..."
24+
#docker-compose build
25+
docker build --build-arg=BUILD="$BUILD" -t mrsarm/django-coleman-mtasks-viewer:${TAG} .

docker-compose.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
version: '3.7'
2+
3+
4+
# Use this compose file as reference or to build the image,
5+
# but better to start the app from the main django-coleman compose
6+
services:
7+
django-coleman-mtasks-viewer:
8+
image: "mrsarm/django-coleman-mtasks-viewer:${TAG:-latest}"
9+
build:
10+
context: .
11+
args:
12+
- BUILD
13+
ports:
14+
- "8888:8888"
15+
environment:
16+
- PORT
17+
- DCOLEMAN_ENDPOINT
18+
- DCOLEMAN_TASKS_VIEWER_HASH_SALT
19+
- DCOLEMAN_MASTER_TOKEN
20+
extra_hosts:
21+
- "django-coleman:${DJANGO_COLEMAN_IP}"

0 commit comments

Comments
 (0)